diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 1f8454a39c..03f1230c4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel +import com.vitorpamplona.amethyst.ui.screen.prepareSharedViewModel import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser @@ -69,8 +70,7 @@ class MainActivity : AppCompatActivity() { Log.d("Lifetime Event", "MainActivity.onCreate") setContent { - val sharedPreferencesViewModel = prepareSharedViewModel(act = this) - + val sharedPreferencesViewModel = prepareSharedViewModel() AmethystTheme(sharedPreferencesViewModel) { val accountStateViewModel: AccountStateViewModel = viewModel() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index 728a4c056a..8f66fb101a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -24,24 +24,16 @@ import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.lifecycle.ViewModelStore -import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoggedInPage import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen import com.vitorpamplona.amethyst.ui.stringRes @@ -56,61 +48,54 @@ fun AccountScreen( Crossfade( targetState = accountState, animationSpec = tween(durationMillis = 100), - label = "AccountState", ) { state -> when (state) { - is AccountState.Loading -> { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, - ) { - LoadingAccounts() - } - } - is AccountState.LoggedOff -> { // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, - ) { - LoginOrSignupScreen(null, accountStateViewModel, isFirstLogin = true) - } - } - is AccountState.LoggedIn -> { - CompositionLocalProvider( - LocalViewModelStoreOwner provides state.currentViewModelStore, - ) { - LoggedInPage( - state.accountSettings, - state.route, - accountStateViewModel, - sharedPreferencesViewModel, - ) - } - - DisposableEffect(key1 = accountState) { - onDispose { - state.currentViewModelStore.viewModelStore.clear() - } - } - } + is AccountState.Loading -> LoadingSetup() + is AccountState.LoggedOff -> LoggedOffSetup(accountStateViewModel) + is AccountState.LoggedIn -> LoggedInSetup(state, accountStateViewModel, sharedPreferencesViewModel) } } } -class AccountCentricViewModelStore( - val accountSettings: AccountSettings, -) : ViewModelStoreOwner { - override val viewModelStore = ViewModelStore() +@Composable +fun LoadingSetup() { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + Column( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(stringRes(R.string.loading_account)) + } + } } @Composable -fun LoadingAccounts() { - Column( - Modifier.fillMaxHeight().fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, +fun LoggedOffSetup(accountStateViewModel: AccountStateViewModel) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, ) { - Text(stringRes(R.string.loading_account)) + LoginOrSignupScreen(null, accountStateViewModel, isFirstLogin = true) + } +} + +@Composable +fun LoggedInSetup( + state: AccountState.LoggedIn, + accountStateViewModel: AccountStateViewModel, + sharedPreferencesViewModel: SharedPreferencesViewModel, +) { + SetAccountCentricViewModelStore(state) { + LoggedInPage( + state.accountSettings, + state.route, + accountStateViewModel, + sharedPreferencesViewModel, + ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt index e7d4f47465..373e531fe4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt @@ -20,7 +20,13 @@ */ package com.vitorpamplona.amethyst.ui.screen +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import com.vitorpamplona.amethyst.model.AccountSettings sealed class AccountState { @@ -33,6 +39,28 @@ sealed class AccountState { val accountSettings: AccountSettings, var route: String? = null, ) : AccountState() { - val currentViewModelStore = AccountCentricViewModelStore(accountSettings) + val currentViewModelStore = AccountCentricViewModelStore() } } + +@Composable +fun SetAccountCentricViewModelStore( + state: AccountState.LoggedIn, + content: @Composable () -> Unit, +) { + CompositionLocalProvider( + LocalViewModelStoreOwner provides state.currentViewModelStore, + ) { + content() + } + + DisposableEffect(key1 = state) { + onDispose { + state.currentViewModelStore.viewModelStore.clear() + } + } +} + +class AccountCentricViewModelStore : ViewModelStoreOwner { + override val viewModelStore = ViewModelStore() +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/AppScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesComposables.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/AppScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesComposables.kt index 4651d43551..45c48693a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/AppScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesComposables.kt @@ -18,9 +18,8 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui +package com.vitorpamplona.amethyst.ui.screen -import android.app.Activity import android.content.Context import android.net.ConnectivityManager import android.net.Network @@ -36,33 +35,39 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.adaptive.calculateDisplayFeatures -import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel +import com.vitorpamplona.amethyst.ui.components.getActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Composable -fun prepareSharedViewModel(act: Activity): SharedPreferencesViewModel { +fun prepareSharedViewModel(): SharedPreferencesViewModel { val sharedPreferencesViewModel: SharedPreferencesViewModel = viewModel() - val displayFeatures = calculateDisplayFeatures(act) - val windowSizeClass = calculateWindowSizeClass(act) - LaunchedEffect(key1 = sharedPreferencesViewModel) { sharedPreferencesViewModel.init() } - LaunchedEffect(sharedPreferencesViewModel, displayFeatures, windowSizeClass) { - sharedPreferencesViewModel.updateDisplaySettings(windowSizeClass, displayFeatures) - } - + MonitorDisplaySize(sharedPreferencesViewModel) ManageConnectivity(sharedPreferencesViewModel) return sharedPreferencesViewModel } +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +fun MonitorDisplaySize(sharedPreferencesViewModel: SharedPreferencesViewModel) { + val act = LocalContext.current.getActivity() + + val displayFeatures = calculateDisplayFeatures(act) + val windowSizeClass = calculateWindowSizeClass(act) + + LaunchedEffect(sharedPreferencesViewModel, displayFeatures, windowSizeClass) { + sharedPreferencesViewModel.updateDisplaySettings(windowSizeClass, displayFeatures) + } +} + @Composable fun ManageConnectivity(sharedPreferencesViewModel: SharedPreferencesViewModel) { val context = LocalContext.current diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt index 314dcb64a5..2a9959ca06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt @@ -25,10 +25,6 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.core.os.LocaleListCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -44,78 +40,13 @@ import com.vitorpamplona.amethyst.model.ThemeType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -@Stable -class SettingsState { - var theme by mutableStateOf(ThemeType.SYSTEM) - var language by mutableStateOf(null) - - var automaticallyShowImages by mutableStateOf(ConnectivityType.ALWAYS) - var automaticallyStartPlayback by mutableStateOf(ConnectivityType.ALWAYS) - var automaticallyShowUrlPreview by mutableStateOf(ConnectivityType.ALWAYS) - var automaticallyHideNavigationBars by mutableStateOf(BooleanType.ALWAYS) - var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS) - var dontShowPushNotificationSelector by mutableStateOf(false) - var dontAskForNotificationPermissions by mutableStateOf(false) - var featureSet by mutableStateOf(FeatureSetType.SIMPLIFIED) - var gallerySet by mutableStateOf(ProfileGalleryType.CLASSIC) - - var isOnMobileOrMeteredConnection by mutableStateOf(false) - var currentNetworkId by mutableStateOf(0L) - - var windowSizeClass = mutableStateOf(null) - var displayFeatures = mutableStateOf>(emptyList()) - - val showProfilePictures = - derivedStateOf { - when (automaticallyShowProfilePictures) { - ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection - ConnectivityType.NEVER -> false - ConnectivityType.ALWAYS -> true - } - } - - val modernGalleryStyle = - derivedStateOf { - when (gallerySet) { - ProfileGalleryType.CLASSIC -> false - ProfileGalleryType.MODERN -> true - } - } - - val showUrlPreview = - derivedStateOf { - when (automaticallyShowUrlPreview) { - ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection - ConnectivityType.NEVER -> false - ConnectivityType.ALWAYS -> true - } - } - - val startVideoPlayback = - derivedStateOf { - when (automaticallyStartPlayback) { - ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection - ConnectivityType.NEVER -> false - ConnectivityType.ALWAYS -> true - } - } - - val showImages = - derivedStateOf { - when (automaticallyShowImages) { - ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection - ConnectivityType.NEVER -> false - ConnectivityType.ALWAYS -> true - } - } -} - @Stable class SharedPreferencesViewModel : ViewModel() { - val sharedPrefs: SettingsState = SettingsState() + val sharedPrefs: SharedSettingsState = SharedSettingsState() fun init() { viewModelScope.launch(Dispatchers.IO) { + Log.d("SharedPreferencesViewModel", "init") val savedSettings = LocalPreferences.loadSharedSettings() ?: Settings() @@ -252,6 +183,7 @@ class SharedPreferencesViewModel : ViewModel() { fun saveSharedSettings() { viewModelScope.launch(Dispatchers.IO) { + Log.d("SharedPreferencesViewModel", "Saving Shared Settings") LocalPreferences.saveSharedSettings( Settings( sharedPrefs.theme, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedSettingsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedSettingsState.kt new file mode 100644 index 0000000000..8dc707f784 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedSettingsState.kt @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen + +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.window.layout.DisplayFeature +import com.vitorpamplona.amethyst.model.BooleanType +import com.vitorpamplona.amethyst.model.ConnectivityType +import com.vitorpamplona.amethyst.model.FeatureSetType +import com.vitorpamplona.amethyst.model.ProfileGalleryType +import com.vitorpamplona.amethyst.model.ThemeType + +@Stable +class SharedSettingsState { + var theme by mutableStateOf(ThemeType.SYSTEM) + var language by mutableStateOf(null) + + var automaticallyShowImages by mutableStateOf(ConnectivityType.ALWAYS) + var automaticallyStartPlayback by mutableStateOf(ConnectivityType.ALWAYS) + var automaticallyShowUrlPreview by mutableStateOf(ConnectivityType.ALWAYS) + var automaticallyHideNavigationBars by mutableStateOf(BooleanType.ALWAYS) + var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS) + var dontShowPushNotificationSelector by mutableStateOf(false) + var dontAskForNotificationPermissions by mutableStateOf(false) + var featureSet by mutableStateOf(FeatureSetType.SIMPLIFIED) + var gallerySet by mutableStateOf(ProfileGalleryType.CLASSIC) + + var isOnMobileOrMeteredConnection by mutableStateOf(false) + var currentNetworkId by mutableStateOf(0L) + + var windowSizeClass = mutableStateOf(null) + var displayFeatures = mutableStateOf>(emptyList()) + + val showProfilePictures = + derivedStateOf { + when (automaticallyShowProfilePictures) { + ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection + ConnectivityType.NEVER -> false + ConnectivityType.ALWAYS -> true + } + } + + val modernGalleryStyle = + derivedStateOf { + when (gallerySet) { + ProfileGalleryType.CLASSIC -> false + ProfileGalleryType.MODERN -> true + } + } + + val showUrlPreview = + derivedStateOf { + when (automaticallyShowUrlPreview) { + ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection + ConnectivityType.NEVER -> false + ConnectivityType.ALWAYS -> true + } + } + + val startVideoPlayback = + derivedStateOf { + when (automaticallyStartPlayback) { + ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection + ConnectivityType.NEVER -> false + ConnectivityType.ALWAYS -> true + } + } + + val showImages = + derivedStateOf { + when (automaticallyShowImages) { + ConnectivityType.WIFI_ONLY -> !isOnMobileOrMeteredConnection + ConnectivityType.NEVER -> false + ConnectivityType.ALWAYS -> true + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 94e9686271..b1e7cbcfe1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -67,8 +67,8 @@ import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.showAmountInteger -import com.vitorpamplona.amethyst.ui.screen.SettingsState import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel +import com.vitorpamplona.amethyst.ui.screen.SharedSettingsState import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedState import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap import com.vitorpamplona.amethyst.ui.stringRes @@ -140,7 +140,7 @@ import kotlinx.coroutines.withContext @Stable class AccountViewModel( accountSettings: AccountSettings, - val settings: SettingsState, + val settings: SharedSettingsState, ) : ViewModel(), Dao { val account = Account(accountSettings, accountSettings.createSigner(), viewModelScope) @@ -1281,7 +1281,7 @@ class AccountViewModel( class Factory( val accountSettings: AccountSettings, - val settings: SettingsState, + val settings: SharedSettingsState, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): AccountViewModel = AccountViewModel(accountSettings, settings) as AccountViewModel }