Replace appcenter with google services

This commit is contained in:
世界 2023-10-31 13:44:42 +08:00
parent 0f9441c650
commit 446ffa4a4d
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
14 changed files with 182 additions and 187 deletions

3
.gitignore vendored
View file

@ -8,4 +8,5 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
/app/libs/ /app/libs/
/service-account-credentials.json

View file

@ -5,6 +5,9 @@ plugins {
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-parcelize' id 'kotlin-parcelize'
id 'com.google.devtools.ksp' id 'com.google.devtools.ksp'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'com.github.triplet.play'
} }
android { android {
@ -39,13 +42,15 @@ android {
if (getProps("KEYSTORE_PASS") != "") { if (getProps("KEYSTORE_PASS") != "") {
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
buildConfigField("String", "APPCENTER_SECRET", "\"" + getProps("APPCENTER_SECRET") + "\"")
} }
release { release {
minifyEnabled true minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release 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:editorkit:2.2.0'
implementation 'com.blacksquircle.ui:language-json: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') { implementation('org.smali:dexlib2:2.5.2') {
exclude group: 'com.google.guava', module: 'guava' exclude group: 'com.google.guava', module: 'guava'
} }
implementation('com.google.guava:guava:32.1.2-android') 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") != "") { 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 { tasks.withType(KotlinCompile.class).configureEach {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"

29
app/google-services.json Normal file
View file

@ -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"
}

View file

@ -39,6 +39,10 @@
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -173,11 +177,6 @@
android:resource="@xml/cache_paths" /> android:resource="@xml/cache_paths" />
</provider> </provider>
<receiver
android:name="com.microsoft.appcenter.distribute.UpdateReceiver"
tools:node="remove" />
</application> </application>
</manifest> </manifest>

View file

@ -4,7 +4,7 @@ object SettingsKey {
const val SELECTED_PROFILE = "selected_profile" const val SELECTED_PROFILE = "selected_profile"
const val SERVICE_MODE = "service_mode" 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 CHECK_UPDATE_ENABLED = "check_update_enabled"
const val DISABLE_MEMORY_LIMIT = "disable_memory_limit" const val DISABLE_MEMORY_LIMIT = "disable_memory_limit"

View file

@ -38,11 +38,11 @@ object Settings {
var serviceMode by dataStore.string(SettingsKey.SERVICE_MODE) { ServiceMode.NORMAL } var serviceMode by dataStore.string(SettingsKey.SERVICE_MODE) { ServiceMode.NORMAL }
var startedByUser by dataStore.boolean(SettingsKey.STARTED_BY_USER) var startedByUser by dataStore.boolean(SettingsKey.STARTED_BY_USER)
const val ANALYSIS_UNKNOWN = -1 const val ERROR_REPORTING_UNKNOWN = -1
const val ANALYSIS_ALLOWED = 0 const val ERROR_REPORTING_ALLOWED = 0
const val ANALYSIS_DISALLOWED = 1 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 checkUpdateEnabled by dataStore.boolean(SettingsKey.CHECK_UPDATE_ENABLED) { true }
var disableMemoryLimit by dataStore.boolean(SettingsKey.DISABLE_MEMORY_LIMIT) var disableMemoryLimit by dataStore.boolean(SettingsKey.DISABLE_MEMORY_LIMIT)

View file

@ -2,12 +2,11 @@ package io.nekohasekai.sfa.ui
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.util.Log
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -18,18 +17,16 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.microsoft.appcenter.AppCenter import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.microsoft.appcenter.analytics.Analytics import com.google.android.play.core.appupdate.AppUpdateOptions
import com.microsoft.appcenter.crashes.Crashes import com.google.android.play.core.install.model.AppUpdateType
import com.microsoft.appcenter.distribute.Distribute import com.google.android.play.core.install.model.InstallStatus
import com.microsoft.appcenter.distribute.DistributeListener import com.google.android.play.core.install.model.UpdateAvailability
import com.microsoft.appcenter.distribute.ReleaseDetails import com.google.firebase.crashlytics.ktx.crashlytics
import com.microsoft.appcenter.distribute.UpdateAction import com.google.firebase.ktx.Firebase
import com.microsoft.appcenter.utils.AppNameHelper
import io.nekohasekai.libbox.Libbox import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.ProfileContent import io.nekohasekai.libbox.ProfileContent
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.BuildConfig
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.ServiceConnection import io.nekohasekai.sfa.bg.ServiceConnection
import io.nekohasekai.sfa.bg.ServiceNotification 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.profile.NewProfileActivity
import io.nekohasekai.sfa.ui.shared.AbstractActivity import io.nekohasekai.sfa.ui.shared.AbstractActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.Date import java.util.Date
import java.util.LinkedList import java.util.LinkedList
class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeListener { class MainActivity : AbstractActivity(), ServiceConnection.Callback {
companion object { companion object {
private const val TAG = "MyActivity" private const val TAG = "MainActivity"
} }
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@ -86,7 +82,7 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeL
binding.navView.setupWithNavController(navController) binding.navView.setupWithNavController(navController)
reconnect() reconnect()
startAnalysis() startIntegration()
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -182,109 +178,76 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback, DistributeL
connection.reconnect() connection.reconnect()
} }
private fun startAnalysis() { private fun startIntegration() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
when (Settings.analyticsAllowed) { if (Settings.errorReportingEnabled == Settings.ERROR_REPORTING_UNKNOWN) {
Settings.ANALYSIS_UNKNOWN -> { withContext(Dispatchers.Main) {
withContext(Dispatchers.Main) { confirmErrorReportingIntegration()
showAnalysisDialog()
}
}
Settings.ANALYSIS_ALLOWED -> {
startAnalysisInternal()
} }
} else if (Settings.checkUpdateEnabled) {
checkUpdate()
} }
} }
} }
private fun showAnalysisDialog() { private fun checkUpdate() {
val builder = MaterialAlertDialogBuilder(this) val appUpdateManager = AppUpdateManagerFactory.create(this)
.setTitle(getString(R.string.analytics_title)) val appUpdateInfoTask = appUpdateManager.appUpdateInfo
.setMessage(getString(R.string.analytics_message)) appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
.setPositiveButton(android.R.string.ok) { _, _ -> when (appUpdateInfo.updateAvailability()) {
lifecycleScope.launch(Dispatchers.IO) { UpdateAvailability.UPDATE_NOT_AVAILABLE -> {
Settings.analyticsAllowed = Settings.ANALYSIS_ALLOWED Log.d(TAG, "checkUpdate: not available")
startAnalysisInternal() }
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) { 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() } 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") @SuppressLint("NewApi")
fun startService() { fun startService() {
if (!ServiceNotification.checkPermission()) { if (!ServiceNotification.checkPermission()) {

View file

@ -11,8 +11,8 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.microsoft.appcenter.AppCenter import com.google.firebase.crashlytics.ktx.crashlytics
import com.microsoft.appcenter.distribute.Distribute import com.google.firebase.ktx.Firebase
import io.nekohasekai.libbox.Libbox import io.nekohasekai.libbox.Libbox
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
@ -60,31 +60,18 @@ class SettingsFragment : Fragment() {
reloadSettings() 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 { binding.checkUpdateEnabled.addTextChangedListener {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val newValue = EnabledType.valueOf(it).boolValue val newValue = EnabledType.valueOf(it).boolValue
Settings.checkUpdateEnabled = newValue 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 { binding.disableMemoryLimit.addTextChangedListener {
@ -128,23 +115,22 @@ class SettingsFragment : Fragment() {
(activity.getExternalFilesDir(null) ?: activity.filesDir) (activity.getExternalFilesDir(null) ?: activity.filesDir)
.walkTopDown().filter { it.isFile }.map { it.length() }.sum() .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 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) Application.powerManager.isIgnoringBatteryOptimizations(Application.application.packageName)
} else { } else {
true true
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.dataSizeText.text = dataSize binding.dataSizeText.text = dataSize
binding.appCenterEnabled.text = EnabledType.from(appCenterEnabled).name binding.errorReportingEnabled.text = EnabledType.from(errorReportingEnabled).name
binding.appCenterEnabled.setSimpleItems(R.array.enabled) binding.errorReportingEnabled.setSimpleItems(R.array.enabled)
binding.checkUpdateEnabled.isEnabled = appCenterEnabled
binding.checkUpdateEnabled.text = EnabledType.from(checkUpdateEnabled).name binding.checkUpdateEnabled.text = EnabledType.from(checkUpdateEnabled).name
binding.checkUpdateEnabled.setSimpleItems(R.array.enabled) binding.checkUpdateEnabled.setSimpleItems(R.array.enabled)
binding.disableMemoryLimit.text = EnabledType.from(!Settings.disableMemoryLimit).name binding.disableMemoryLimit.text = EnabledType.from(!Settings.disableMemoryLimit).name
binding.disableMemoryLimit.setSimpleItems(R.array.enabled) binding.disableMemoryLimit.setSimpleItems(R.array.enabled)
binding.backgroundPermissionCard.isGone = removeBackgroudPermissionPage binding.backgroundPermissionCard.isGone = removeBackgroundPermissionPage
} }
} }

View file

@ -282,7 +282,7 @@ class PerAppProxyActivity : AbstractActivity() {
if (scanResult.isEmpty()) { if (scanResult.isEmpty()) {
MaterialAlertDialogBuilder(this@PerAppProxyActivity) MaterialAlertDialogBuilder(this@PerAppProxyActivity)
.setTitle(R.string.message) .setTitle(R.string.title_scan_result)
.setMessage(R.string.message_scan_app_no_apps_found) .setMessage(R.string.message_scan_app_no_apps_found)
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.show() .show()

View file

@ -0,0 +1 @@
Fixes and improvements

View file

@ -30,17 +30,17 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/title_app_center" android:text="@string/title_app_settings"
android:layout_marginBottom="8dp"
android:textAppearance="?attr/textAppearanceTitleLarge" /> android:textAppearance="?attr/textAppearanceTitleLarge" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/appCenterEnabled" android:id="@+id/checkUpdateEnabled"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu" style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="8dp"
android:hint="@string/enabled"> android:hint="@string/check_update">
<AutoCompleteTextView <AutoCompleteTextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -52,12 +52,12 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/checkUpdateEnabled" android:id="@+id/errorReportingEnabled"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu" style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/check_update"> android:hint="@string/error_reporting">
<AutoCompleteTextView <AutoCompleteTextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -95,27 +95,11 @@
</TextView> </TextView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/disableMemoryLimit"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/memory_limit">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:text="@string/enabled"
app:simpleItems="@array/enabled" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp"> android:layout_marginTop="8dp">
<TextView <TextView
style="?attr/textAppearanceTitleSmall" style="?attr/textAppearanceTitleSmall"
@ -154,10 +138,27 @@
</LinearLayout> </LinearLayout>
<LinearLayout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/disableMemoryLimit"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/memory_limit">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:text="@string/enabled"
app:simpleItems="@array/enabled" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end" android:gravity="center_vertical|end"
android:orientation="horizontal"> android:orientation="horizontal">

View file

@ -2,6 +2,8 @@
<string name="app_name">sing-box</string> <string name="app_name">sing-box</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="ok">Ok</string>
<string name="no_thanks">No, thanks</string>
<string name="title_dashboard">Dashboard</string> <string name="title_dashboard">Dashboard</string>
<string name="title_configuration">Profiles</string> <string name="title_configuration">Profiles</string>
@ -80,15 +82,10 @@
<string name="core_version">Version</string> <string name="core_version">Version</string>
<string name="core">Core</string> <string name="core">Core</string>
<string name="core_data_size">Data Size</string> <string name="core_data_size">Data Size</string>
<string name="analytics_title">Analytics</string>
<string name="analytics_message">Would you like to give SFA permission to collect analytics, send crash reports, and check update through AppCenter?</string>
<string name="no_thanks">No, thanks</string>
<string name="check_update">Check Update</string> <string name="check_update">Check Update</string>
<string name="title_app_center">App Center</string> <string name="error_reporting">Error Reporting</string>
<string name="title_feedback">Feedback</string> <string name="error_reporting_message">Would you like to allow sing to send error reports to developers via Firebase Crashlytics?</string>
<string name="message">Message</string> <string name="title_app_settings">App Settings</string>
<string name="send">Send</string>
<string name="send_feedback">Send Feedback</string>
<string name="about_title">About</string> <string name="about_title">About</string>
<string name="app_description">Android client for sing-box, the universal proxy platform.</string> <string name="app_description">Android client for sing-box, the universal proxy platform.</string>
<string name="documentation_button">Documentation</string> <string name="documentation_button">Documentation</string>

View file

@ -2,6 +2,8 @@
buildscript { buildscript {
dependencies { dependencies {
classpath "gradle.plugin.com.betomorrow.gradle:appcenter-plugin:2.0.4" 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 'com.android.library' version '8.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' 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.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
} }

View file

@ -1,7 +1,7 @@
#Sun Aug 20 19:43:30 CST 2023 #Mon Oct 30 15:48:15 CST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists