diff --git a/.gitignore b/.gitignore
index 6ff1684..8951cc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@
.externalNativeBuild
.cxx
local.properties
-/app/libs/
\ No newline at end of file
+/app/libs/
+/service-account-credentials.json
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index ee79e2c..3e1061d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,6 +5,9 @@ plugins {
id 'kotlin-android'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
+ id 'com.google.gms.google-services'
+ id 'com.google.firebase.crashlytics'
+ id 'com.github.triplet.play'
}
android {
@@ -39,13 +42,15 @@ android {
if (getProps("KEYSTORE_PASS") != "") {
signingConfig signingConfigs.release
}
- buildConfigField("String", "APPCENTER_SECRET", "\"" + getProps("APPCENTER_SECRET") + "\"")
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
- buildConfigField("String", "APPCENTER_SECRET", "\"" + getProps("APPCENTER_SECRET") + "\"")
+ firebaseCrashlytics {
+ nativeSymbolUploadEnabled true
+ unstrippedNativeLibsDir true
+ }
}
}
@@ -96,14 +101,14 @@ dependencies {
implementation 'com.blacksquircle.ui:editorkit:2.2.0'
implementation 'com.blacksquircle.ui:language-json:2.2.0'
- implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.2'
- implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.2'
- implementation 'com.microsoft.appcenter:appcenter-distribute:5.0.2'
-
implementation('org.smali:dexlib2:2.5.2') {
exclude group: 'com.google.guava', module: 'guava'
}
implementation('com.google.guava:guava:32.1.2-android')
+
+ implementation platform('com.google.firebase:firebase-bom:32.4.0')
+ implementation 'com.google.firebase:firebase-crashlytics-ktx'
+ implementation 'com.google.android.play:app-update-ktx:2.1.0'
}
if (getProps("APPCENTER_TOKEN") != "") {
@@ -122,6 +127,15 @@ if (getProps("APPCENTER_TOKEN") != "") {
}
}
+def playCredentialsJSON = rootProject.file("service-account-credentials.json")
+if (playCredentialsJSON.exists()) {
+ play {
+ serviceAccountCredentials = playCredentialsJSON
+ defaultToAppBundles = true
+ track = 'beta'
+ }
+}
+
tasks.withType(KotlinCompile.class).configureEach {
kotlinOptions {
jvmTarget = "1.8"
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..474a84b
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "548801210715",
+ "project_id": "sing-b0x",
+ "storage_bucket": "sing-b0x.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:548801210715:android:2c3bce07700eecb54d527e",
+ "android_client_info": {
+ "package_name": "io.nekohasekai.sfa"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDzb5nuF2qyw-AW0opn4Ymi2QGuJ6dZyYo"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 14ca0b9..91b99a7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,6 +39,10 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
+
+
@@ -173,11 +177,6 @@
android:resource="@xml/cache_paths" />
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
index 40e32d9..7d21044 100644
--- a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
@@ -4,7 +4,7 @@ object SettingsKey {
const val SELECTED_PROFILE = "selected_profile"
const val SERVICE_MODE = "service_mode"
- const val ANALYTICS_ALLOWED = "analytics_allowed"
+ const val ERROR_REPORTING_ENABLED = "error_reporting_enabled"
const val CHECK_UPDATE_ENABLED = "check_update_enabled"
const val DISABLE_MEMORY_LIMIT = "disable_memory_limit"
diff --git a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
index 2f7b1a0..fbd9f03 100644
--- a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
@@ -38,11 +38,11 @@ object Settings {
var serviceMode by dataStore.string(SettingsKey.SERVICE_MODE) { ServiceMode.NORMAL }
var startedByUser by dataStore.boolean(SettingsKey.STARTED_BY_USER)
- const val ANALYSIS_UNKNOWN = -1
- const val ANALYSIS_ALLOWED = 0
- const val ANALYSIS_DISALLOWED = 1
+ const val ERROR_REPORTING_UNKNOWN = -1
+ const val ERROR_REPORTING_ALLOWED = 0
+ const val ERROR_REPORTING_DISALLOWED = 1
- var analyticsAllowed by dataStore.int(SettingsKey.ANALYTICS_ALLOWED) { ANALYSIS_UNKNOWN }
+ var errorReportingEnabled by dataStore.int(SettingsKey.ERROR_REPORTING_ENABLED) { ERROR_REPORTING_UNKNOWN }
var checkUpdateEnabled by dataStore.boolean(SettingsKey.CHECK_UPDATE_ENABLED) { true }
var disableMemoryLimit by dataStore.boolean(SettingsKey.DISABLE_MEMORY_LIMIT)
diff --git a/app/src/main/java/io/nekohasekai/sfa/ui/MainActivity.kt b/app/src/main/java/io/nekohasekai/sfa/ui/MainActivity.kt
index 3b01c50..2d6e87d 100644
--- a/app/src/main/java/io/nekohasekai/sfa/ui/MainActivity.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/ui/MainActivity.kt
@@ -2,12 +2,11 @@ package io.nekohasekai.sfa.ui
import android.Manifest
import android.annotation.SuppressLint
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.os.Bundle
-import android.text.TextUtils
+import android.util.Log
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
@@ -18,18 +17,16 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.microsoft.appcenter.AppCenter
-import com.microsoft.appcenter.analytics.Analytics
-import com.microsoft.appcenter.crashes.Crashes
-import com.microsoft.appcenter.distribute.Distribute
-import com.microsoft.appcenter.distribute.DistributeListener
-import com.microsoft.appcenter.distribute.ReleaseDetails
-import com.microsoft.appcenter.distribute.UpdateAction
-import com.microsoft.appcenter.utils.AppNameHelper
+import com.google.android.play.core.appupdate.AppUpdateManagerFactory
+import com.google.android.play.core.appupdate.AppUpdateOptions
+import com.google.android.play.core.install.model.AppUpdateType
+import com.google.android.play.core.install.model.InstallStatus
+import com.google.android.play.core.install.model.UpdateAvailability
+import com.google.firebase.crashlytics.ktx.crashlytics
+import com.google.firebase.ktx.Firebase
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.ProfileContent
import io.nekohasekai.sfa.Application
-import io.nekohasekai.sfa.BuildConfig
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.ServiceConnection
import io.nekohasekai.sfa.bg.ServiceNotification
@@ -45,17 +42,16 @@ import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ui.profile.NewProfileActivity
import io.nekohasekai.sfa.ui.shared.AbstractActivity
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Date
import java.util.LinkedList
-class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeListener {
+class MainActivity : AbstractActivity(), ServiceConnection.Callback {
companion object {
- private const val TAG = "MyActivity"
+ private const val TAG = "MainActivity"
}
private lateinit var binding: ActivityMainBinding
@@ -86,7 +82,7 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeL
binding.navView.setupWithNavController(navController)
reconnect()
- startAnalysis()
+ startIntegration()
}
override fun onNewIntent(intent: Intent) {
@@ -182,109 +178,76 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeL
connection.reconnect()
}
- private fun startAnalysis() {
+ private fun startIntegration() {
lifecycleScope.launch(Dispatchers.IO) {
- when (Settings.analyticsAllowed) {
- Settings.ANALYSIS_UNKNOWN -> {
- withContext(Dispatchers.Main) {
- showAnalysisDialog()
- }
- }
-
- Settings.ANALYSIS_ALLOWED -> {
- startAnalysisInternal()
+ if (Settings.errorReportingEnabled == Settings.ERROR_REPORTING_UNKNOWN) {
+ withContext(Dispatchers.Main) {
+ confirmErrorReportingIntegration()
}
+ } else if (Settings.checkUpdateEnabled) {
+ checkUpdate()
}
}
}
- private fun showAnalysisDialog() {
- val builder = MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.analytics_title))
- .setMessage(getString(R.string.analytics_message))
- .setPositiveButton(android.R.string.ok) { _, _ ->
- lifecycleScope.launch(Dispatchers.IO) {
- Settings.analyticsAllowed = Settings.ANALYSIS_ALLOWED
- startAnalysisInternal()
+ private fun checkUpdate() {
+ val appUpdateManager = AppUpdateManagerFactory.create(this)
+ val appUpdateInfoTask = appUpdateManager.appUpdateInfo
+ appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
+ when (appUpdateInfo.updateAvailability()) {
+ UpdateAvailability.UPDATE_NOT_AVAILABLE -> {
+ Log.d(TAG, "checkUpdate: not available")
+ }
+
+ UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS -> {
+ when (appUpdateInfo.installStatus()) {
+ InstallStatus.DOWNLOADED -> {
+ appUpdateManager.completeUpdate()
+ }
+ }
+ }
+
+ UpdateAvailability.UPDATE_AVAILABLE -> {
+ if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
+ appUpdateManager.startUpdateFlow(
+ appUpdateInfo,
+ this,
+ AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()
+ )
+ } else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
+ appUpdateManager.startUpdateFlow(
+ appUpdateInfo,
+ this,
+ AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
+ )
+ }
+ }
+
+ UpdateAvailability.UNKNOWN -> {
}
}
- .setNegativeButton(getString(R.string.no_thanks)) { _, _ ->
+ }
+ appUpdateInfoTask.addOnFailureListener {
+ Log.e(TAG, "checkUpdate: ", it)
+ }
+ }
+
+ private fun confirmErrorReportingIntegration() {
+ val builder = MaterialAlertDialogBuilder(this).setTitle(getString(R.string.error_reporting))
+ .setMessage(R.string.error_reporting_message)
+ .setPositiveButton(getString(R.string.ok)) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
- Settings.analyticsAllowed = Settings.ANALYSIS_DISALLOWED
+ Settings.errorReportingEnabled = Settings.ERROR_REPORTING_ALLOWED
+ Firebase.crashlytics.setCrashlyticsCollectionEnabled(true)
+ }
+ }.setNegativeButton(getString(R.string.no_thanks)) { _, _ ->
+ lifecycleScope.launch(Dispatchers.IO) {
+ Settings.errorReportingEnabled = Settings.ERROR_REPORTING_DISALLOWED
}
}
runCatching { builder.show() }
}
- suspend fun startAnalysisInternal() {
- if (BuildConfig.APPCENTER_SECRET.isBlank()) {
- return
- }
- Distribute.setListener(this)
- runCatching {
- AppCenter.start(
- application,
- BuildConfig.APPCENTER_SECRET,
- Analytics::class.java,
- Crashes::class.java,
- Distribute::class.java,
- )
- if (!Settings.checkUpdateEnabled) {
- Distribute.disableAutomaticCheckForUpdate()
- }
- }.onFailure {
- withContext(Dispatchers.Main) {
- errorDialogBuilder(it).show()
- }
- }
- }
-
- override fun onReleaseAvailable(activity: Activity, releaseDetails: ReleaseDetails): Boolean {
- lifecycleScope.launch(Dispatchers.Main) {
- delay(2000L)
- runCatching {
- onReleaseAvailable0(releaseDetails)
- }
- }
- return true
- }
-
- private fun onReleaseAvailable0(releaseDetails: ReleaseDetails) {
- val builder = MaterialAlertDialogBuilder(this)
- .setTitle(getString(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_title))
- var message = if (releaseDetails.isMandatoryUpdate) {
- getString(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_message_mandatory)
- } else {
- getString(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_message_optional)
- }
- message = String.format(
- message,
- AppNameHelper.getAppName(this),
- releaseDetails.shortVersion,
- releaseDetails.version
- )
- builder.setMessage(message)
- builder.setPositiveButton(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_download) { _, _ ->
- startActivity(Intent(Intent.ACTION_VIEW, releaseDetails.downloadUrl))
- }
- builder.setCancelable(false)
- if (!releaseDetails.isMandatoryUpdate) {
- builder.setNegativeButton(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_postpone) { _, _ ->
- Distribute.notifyUpdateAction(UpdateAction.POSTPONE)
- }
- }
- if (!TextUtils.isEmpty(releaseDetails.releaseNotes) && releaseDetails.releaseNotesUrl != null) {
- builder.setNeutralButton(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_view_release_notes) { _, _ ->
- startActivity(Intent(Intent.ACTION_VIEW, releaseDetails.releaseNotesUrl))
- }
- }
- builder.show()
- }
-
- override fun onNoReleaseAvailable(activity: Activity) {
- }
-
-
@SuppressLint("NewApi")
fun startService() {
if (!ServiceNotification.checkPermission()) {
diff --git a/app/src/main/java/io/nekohasekai/sfa/ui/main/SettingsFragment.kt b/app/src/main/java/io/nekohasekai/sfa/ui/main/SettingsFragment.kt
index f1921d9..8159c20 100644
--- a/app/src/main/java/io/nekohasekai/sfa/ui/main/SettingsFragment.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/ui/main/SettingsFragment.kt
@@ -11,8 +11,8 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
-import com.microsoft.appcenter.AppCenter
-import com.microsoft.appcenter.distribute.Distribute
+import com.google.firebase.crashlytics.ktx.crashlytics
+import com.google.firebase.ktx.Firebase
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.R
@@ -60,31 +60,18 @@ class SettingsFragment : Fragment() {
reloadSettings()
}
}
- binding.appCenterEnabled.addTextChangedListener {
- lifecycleScope.launch(Dispatchers.IO) {
- val allowed = EnabledType.valueOf(it).boolValue
- Settings.analyticsAllowed =
- if (allowed) Settings.ANALYSIS_ALLOWED else Settings.ANALYSIS_DISALLOWED
- withContext(Dispatchers.Main) {
- binding.checkUpdateEnabled.isEnabled = allowed
- }
- if (!allowed) {
- AppCenter.setEnabled(false)
- } else {
- if (!AppCenter.isConfigured()) {
- activity.startAnalysisInternal()
- }
- AppCenter.setEnabled(true)
- }
- }
- }
binding.checkUpdateEnabled.addTextChangedListener {
lifecycleScope.launch(Dispatchers.IO) {
val newValue = EnabledType.valueOf(it).boolValue
Settings.checkUpdateEnabled = newValue
- if (!newValue) {
- Distribute.disableAutomaticCheckForUpdate()
- }
+ }
+ }
+ binding.errorReportingEnabled.addTextChangedListener {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val newValue = EnabledType.valueOf(it).boolValue
+ Settings.errorReportingEnabled =
+ if (newValue) Settings.ERROR_REPORTING_ALLOWED else Settings.ERROR_REPORTING_DISALLOWED
+ Firebase.crashlytics.setCrashlyticsCollectionEnabled(newValue)
}
}
binding.disableMemoryLimit.addTextChangedListener {
@@ -128,23 +115,22 @@ class SettingsFragment : Fragment() {
(activity.getExternalFilesDir(null) ?: activity.filesDir)
.walkTopDown().filter { it.isFile }.map { it.length() }.sum()
)
- val appCenterEnabled = Settings.analyticsAllowed == Settings.ANALYSIS_ALLOWED
+ val errorReportingEnabled = Settings.errorReportingEnabled == Settings.ERROR_REPORTING_ALLOWED
val checkUpdateEnabled = Settings.checkUpdateEnabled
- val removeBackgroudPermissionPage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val removeBackgroundPermissionPage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Application.powerManager.isIgnoringBatteryOptimizations(Application.application.packageName)
} else {
true
}
withContext(Dispatchers.Main) {
binding.dataSizeText.text = dataSize
- binding.appCenterEnabled.text = EnabledType.from(appCenterEnabled).name
- binding.appCenterEnabled.setSimpleItems(R.array.enabled)
- binding.checkUpdateEnabled.isEnabled = appCenterEnabled
+ binding.errorReportingEnabled.text = EnabledType.from(errorReportingEnabled).name
+ binding.errorReportingEnabled.setSimpleItems(R.array.enabled)
binding.checkUpdateEnabled.text = EnabledType.from(checkUpdateEnabled).name
binding.checkUpdateEnabled.setSimpleItems(R.array.enabled)
binding.disableMemoryLimit.text = EnabledType.from(!Settings.disableMemoryLimit).name
binding.disableMemoryLimit.setSimpleItems(R.array.enabled)
- binding.backgroundPermissionCard.isGone = removeBackgroudPermissionPage
+ binding.backgroundPermissionCard.isGone = removeBackgroundPermissionPage
}
}
diff --git a/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/PerAppProxyActivity.kt b/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/PerAppProxyActivity.kt
index 4103798..bd8b8c8 100644
--- a/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/PerAppProxyActivity.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/PerAppProxyActivity.kt
@@ -282,7 +282,7 @@ class PerAppProxyActivity : AbstractActivity() {
if (scanResult.isEmpty()) {
MaterialAlertDialogBuilder(this@PerAppProxyActivity)
- .setTitle(R.string.message)
+ .setTitle(R.string.title_scan_result)
.setMessage(R.string.message_scan_app_no_apps_found)
.setPositiveButton(android.R.string.ok, null)
.show()
diff --git a/app/src/main/play/release-notes/en-US/beta.txt b/app/src/main/play/release-notes/en-US/beta.txt
new file mode 100644
index 0000000..0663726
--- /dev/null
+++ b/app/src/main/play/release-notes/en-US/beta.txt
@@ -0,0 +1 @@
+Fixes and improvements
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 114dab5..359b1ed 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -30,17 +30,17 @@
-
+ android:layout_marginTop="8dp"
+ android:hint="@string/check_update">
+ android:hint="@string/error_reporting">
-
-
-
-
-
+ android:layout_marginTop="8dp">
-
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 332702c..f3b1b72 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,6 +2,8 @@
sing-box
Stop
+ Ok
+ No, thanks
Dashboard
Profiles
@@ -80,15 +82,10 @@
Version
Core
Data Size
- Analytics
- Would you like to give SFA permission to collect analytics, send crash reports, and check update through AppCenter?
- No, thanks
Check Update
- App Center
- Feedback
- Message
- Send
- Send Feedback
+ Error Reporting
+ Would you like to allow sing to send error reports to developers via Firebase Crashlytics?
+ App Settings
About
Android client for sing-box, the universal proxy platform.
Documentation
diff --git a/build.gradle b/build.gradle
index 17971be..4a4020f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,8 @@
buildscript {
dependencies {
classpath "gradle.plugin.com.betomorrow.gradle:appcenter-plugin:2.0.4"
+ // https://github.com/invertase/react-native-firebase/issues/6983
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
}
}
@@ -10,5 +12,7 @@ plugins {
id 'com.android.library' version '8.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
id 'com.google.devtools.ksp' version '1.9.0-1.0.12' apply false
+ id 'com.google.gms.google-services' version '4.4.0' apply false
+ id 'com.github.triplet.play' version '3.8.4' apply false
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 79b86cd..c8b7bec 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
-#Sun Aug 20 19:43:30 CST 2023
+#Mon Oct 30 15:48:15 CST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists