diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt index 7f3892430..111ecb675 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt @@ -38,6 +38,7 @@ import io.nekohasekai.sagernet.widget.ListListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.coroutines.coroutineContext @@ -174,7 +175,7 @@ class AppListActivity : ThemedActivity() { @UiThread private fun loadApps() { loader?.cancel() - loader = lifecycleScope.launchWhenCreated { + loader = lifecycleScope.launch { loading.crossFadeFrom(binding.list) val adapter = binding.list.adapter as AppsAdapter withContext(Dispatchers.IO) { adapter.reload() } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt index 22d7789ca..aa4995254 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt @@ -384,10 +384,6 @@ class AssetsActivity : ThemedActivity() { return true } - override fun onBackPressed() { - finish() - } - override fun onResume() { super.onResume() diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 86a6fdfb5..589898ea4 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -27,6 +27,7 @@ import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.core.graphics.ColorUtils import androidx.core.net.toUri +import androidx.core.os.BundleCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.size @@ -1180,7 +1181,7 @@ class ConfigurationFragment @JvmOverloads constructor( override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) - savedInstanceState?.getParcelable("proxyGroup")?.also { + savedInstanceState?.let { BundleCompat.getParcelable(it, "proxyGroup", ProxyGroup::class.java) }?.also { proxyGroup = it onViewCreated(requireView(), null) } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt index 5c827eda0..e1bfa029c 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt @@ -10,6 +10,7 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Toast +import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog @@ -246,6 +247,11 @@ class GroupSettingsActivity( @SuppressLint("CommitTransaction") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + onBackPressedDispatcher.addCallback(this) { + if (needSave()) { + UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) + } else finish() + } setSupportActionBar(findViewById(R.id.toolbar)) supportActionBar?.apply { setTitle(R.string.group_settings) @@ -330,14 +336,10 @@ class GroupSettingsActivity( override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) - override fun onBackPressed() { - if (needSave()) { - UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) - } else super.onBackPressed() - } - override fun onSupportNavigateUp(): Boolean { - if (!super.onSupportNavigateUp()) finish() + // Route the action-bar up button through the back dispatcher so it honors the + // unsaved-changes guard instead of finishing directly (avoids data loss). + onBackPressedDispatcher.onBackPressed() return true } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt index 20edf1a1e..cea8e1093 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt @@ -2,6 +2,7 @@ package io.nekohasekai.sagernet.ui import android.content.Intent import android.os.Bundle +import androidx.core.content.IntentCompat import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.database.ProxyEntity @@ -16,7 +17,7 @@ class ProfileSelectActivity : ThemedActivity(R.layout.layout_empty), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val selected = intent.getParcelableExtra(EXTRA_SELECTED) + val selected = IntentCompat.getParcelableExtra(intent, EXTRA_SELECTED, ProxyEntity::class.java) supportFragmentManager.beginTransaction() .replace( diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt index 94e0b8f7d..46720f8ea 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt @@ -9,6 +9,7 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Toast +import androidx.activity.addCallback import androidx.activity.result.component1 import androidx.activity.result.component2 import androidx.activity.result.contract.ActivityResultContracts @@ -221,6 +222,11 @@ class RouteSettingsActivity( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + onBackPressedDispatcher.addCallback(this) { + if (needSave()) { + UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) + } else finish() + } setSupportActionBar(findViewById(R.id.toolbar)) supportActionBar?.apply { setTitle(R.string.cag_route) @@ -300,14 +306,10 @@ class RouteSettingsActivity( override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) - override fun onBackPressed() { - if (needSave()) { - UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) - } else super.onBackPressed() - } - override fun onSupportNavigateUp(): Boolean { - if (!super.onSupportNavigateUp()) finish() + // Route the action-bar up button through the back dispatcher so it honors the + // unsaved-changes guard instead of finishing directly (avoids data loss). + onBackPressedDispatcher.onBackPressed() return true } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt index e144a26ef..504008d88 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt @@ -7,6 +7,7 @@ import android.view.Menu import android.view.MenuItem import android.view.ViewGroup.MarginLayoutParams import android.widget.LinearLayout +import androidx.activity.addCallback import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -53,6 +54,10 @@ class ConfigEditActivity : ThemedActivity() { @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + onBackPressedDispatcher.addCallback(this) { + if (dirty) UnsavedChangesDialogFragment().apply { key() } + .show(supportFragmentManager, null) else finish() + } intent?.extras?.apply { getString("key")?.let { key = it } @@ -165,13 +170,10 @@ class ConfigEditActivity : ThemedActivity() { } } - override fun onBackPressed() { - if (dirty) UnsavedChangesDialogFragment().apply { key() } - .show(supportFragmentManager, null) else super.onBackPressed() - } - override fun onSupportNavigateUp(): Boolean { - if (!super.onSupportNavigateUp()) finish() + // Route the action-bar up button through the back dispatcher so it honors the + // unsaved-changes guard instead of finishing directly (avoids data loss). + onBackPressedDispatcher.onBackPressed() return true } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt index 6424502ba..2a2babbd7 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt @@ -12,6 +12,7 @@ import android.view.View import android.widget.LinearLayout import android.widget.ScrollView import android.widget.Toast +import androidx.activity.addCallback import androidx.activity.result.component1 import androidx.activity.result.component2 import androidx.activity.result.contract.ActivityResultContracts @@ -92,6 +93,10 @@ abstract class ProfileSettingsActivity( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + onBackPressedDispatcher.addCallback(this) { + if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() } + .show(supportFragmentManager, null) else finish() + } setSupportActionBar(findViewById(R.id.toolbar)) supportActionBar?.apply { setTitle(R.string.profile_config) @@ -174,13 +179,10 @@ abstract class ProfileSettingsActivity( override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) - override fun onBackPressed() { - if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() } - .show(supportFragmentManager, null) else super.onBackPressed() - } - override fun onSupportNavigateUp(): Boolean { - if (!super.onSupportNavigateUp()) finish() + // Route the action-bar up button through the same back dispatcher so it honors + // the unsaved-changes guard instead of finishing directly (avoids data loss). + onBackPressedDispatcher.onBackPressed() return true } diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt index e8e0183f4..6a578a823 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt @@ -12,6 +12,7 @@ import androidx.appcompat.widget.TooltipCompat import androidx.dynamicanimation.animation.DynamicAnimation import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withStarted import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.google.android.material.floatingactionbutton.FloatingActionButton @@ -21,6 +22,7 @@ import io.nekohasekai.sagernet.bg.BaseService import io.nekohasekai.sagernet.ktx.getColorAttr import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.* class ServiceButton @JvmOverloads constructor( @@ -64,10 +66,14 @@ class ServiceButton @JvmOverloads constructor( private val iconConnecting by lazy { AnimatedState(R.drawable.ic_service_connecting) { hideProgress() - delayedAnimation = (context as LifecycleOwner).lifecycleScope.launchWhenStarted { + delayedAnimation = (context as LifecycleOwner).lifecycleScope.launch { delay(context.resources.getInteger(android.R.integer.config_mediumAnimTime) + 1000L) - isIndeterminate = true - show() + // Gate the UI mutation on STARTED so a delayed progress reveal doesn't run + // while the activity is stopped (the old launchWhenStarted suspended here). + (context as LifecycleOwner).withStarted { + isIndeterminate = true + show() + } } } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt index 7cddcfc46..a6cdde97a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt @@ -11,7 +11,7 @@ import android.widget.TextView import androidx.appcompat.widget.TooltipCompat import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.whenStarted +import androidx.lifecycle.withStarted import com.google.android.material.bottomappbar.BottomAppBar import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.bg.BaseService @@ -125,7 +125,7 @@ class StatsBar @JvmOverloads constructor( val activity = context as MainActivity fun postWhenStarted(what: () -> Unit) = activity.lifecycleScope.launch(Dispatchers.Main) { delay(100L) - activity.whenStarted { what() } + activity.withStarted { what() } } if ((state == BaseService.State.Connected).also { hideOnScroll = it }) { postWhenStarted {