From 8429f35c1ebbb07b42aed8c6eef2c79a8fec41cf Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:58:21 -0400 Subject: [PATCH] MVVM-ify logic to display app intro (#2748) --- .../java/com/geeksville/mesh/MainActivity.kt | 30 +++++++------------ .../java/com/geeksville/mesh/model/UIState.kt | 27 ++++++++++++++--- .../main/java/com/geeksville/mesh/ui/Main.kt | 1 + 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 6aca2709e..128579072 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -20,6 +20,7 @@ package com.geeksville.mesh import android.app.PendingIntent import android.app.TaskStackBuilder import android.content.Intent +import android.content.SharedPreferences import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.graphics.Color @@ -39,12 +40,10 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalView -import androidx.core.content.edit import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.android.GeeksvilleApplication import com.geeksville.mesh.android.Logging import com.geeksville.mesh.model.BluetoothViewModel @@ -71,7 +70,7 @@ class MainActivity : // This is aware of the Activity lifecycle and handles binding to the mesh service. @Inject internal lateinit var meshServiceClient: MeshServiceClient - private var showAppIntro by mutableStateOf(false) + @Inject internal lateinit var uiPrefs: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -85,15 +84,13 @@ class MainActivity : } super.onCreate(savedInstanceState) - val prefs = UIViewModel.getPreferences(this) + if (savedInstanceState == null) { - val lang = prefs.getString("lang", LanguageUtils.SYSTEM_DEFAULT) - if (lang != LanguageUtils.SYSTEM_MANAGED) LanguageUtils.migrateLanguagePrefs(prefs) + val lang = uiPrefs.getString("lang", LanguageUtils.SYSTEM_DEFAULT) + if (lang != LanguageUtils.SYSTEM_MANAGED) LanguageUtils.migrateLanguagePrefs(uiPrefs) info("in-app language is ${LanguageUtils.getLocale()}") - if (!prefs.getBoolean("app_intro_completed", false)) { - showAppIntro = true - } else { + if (uiPrefs.getBoolean("app_intro_completed", false)) { (application as GeeksvilleApplication).askToRate(this) } } @@ -115,11 +112,11 @@ class MainActivity : SideEffect { AppCompatDelegate.setDefaultNightMode(theme) } } + val showAppIntro by model.showAppIntro.collectAsStateWithLifecycle() if (showAppIntro) { AppIntroductionScreen( onDone = { - prefs.edit { putBoolean("app_intro_completed", true) } - showAppIntro = false + model.onAppIntroCompleted() (application as GeeksvilleApplication).askToRate(this@MainActivity) }, ) @@ -246,11 +243,7 @@ class MainActivity : chooseLangDialog() } - MainMenuAction.SHOW_INTRO -> { - showAppIntro = true - } - - else -> {} + else -> warn("Unexpected action: $action") } } @@ -273,8 +266,7 @@ class MainActivity : getString(R.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, ) - val prefs = UIViewModel.getPreferences(this) - val theme = prefs.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + val theme = uiPrefs.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) debug("Theme from prefs: $theme") model.showAlert( title = getString(R.string.choose_theme), diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 9b022a9ca..f5f0ddd13 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -18,7 +18,6 @@ package com.geeksville.mesh.model import android.app.Application -import android.content.Context import android.content.SharedPreferences import android.net.Uri import android.os.RemoteException @@ -64,6 +63,7 @@ import com.geeksville.mesh.repository.radio.MeshActivity import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshServiceNotifications import com.geeksville.mesh.service.ServiceAction +import com.geeksville.mesh.ui.MainMenuAction import com.geeksville.mesh.ui.node.components.NodeMenuAction import com.geeksville.mesh.util.getShortDate import com.geeksville.mesh.util.positionToMeter @@ -86,6 +86,7 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.BufferedWriter @@ -679,9 +680,6 @@ constructor( } companion object { - fun getPreferences(context: Context): SharedPreferences = - context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE) - const val HAS_SHOWN_NOT_PAIRED_WARNING_PREF = "has_shown_not_paired_warning" } @@ -990,4 +988,25 @@ constructor( fun setNodeFilterText(text: String) { nodeFilterText.value = text } + + // region Main menu actions logic + + private val _showAppIntro: MutableStateFlow = + MutableStateFlow(preferences.getBoolean("app_intro_completed", false).not()) + val showAppIntro: StateFlow = _showAppIntro.asStateFlow() + + fun onMainMenuAction(action: MainMenuAction) { + when (action) { + MainMenuAction.SHOW_INTRO -> _showAppIntro.update { true } + + else -> Unit + } + } + + // endregion + + fun onAppIntroCompleted() { + preferences.edit { putBoolean("app_intro_completed", true) } + _showAppIntro.update { false } + } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 93a5e099a..7af568544 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -359,6 +359,7 @@ fun MainScreen( MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel) MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig()) MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) + MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action) else -> onAction(action) } } else if (action is NodeMenuAction) {