diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt index e9101cd..1515407 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt @@ -39,6 +39,7 @@ import androidx.navigation3.ui.NavDisplay import kotlinx.coroutines.launch import su.reya.coop.repository.AccountRepository import su.reya.coop.repository.ChatRepository +import su.reya.coop.repository.SettingsRepository import su.reya.coop.screens.ContactListScreen import su.reya.coop.screens.HomeScreen import su.reya.coop.screens.ImportScreen @@ -56,11 +57,16 @@ import su.reya.coop.viewmodel.AccountViewModel import su.reya.coop.viewmodel.ChatScreenViewModel import su.reya.coop.viewmodel.ChatViewModel import su.reya.coop.viewmodel.ProfileCache +import su.reya.coop.viewmodel.SettingsViewModel val LocalProfileCache = staticCompositionLocalOf { error("No ProfileCache provided") } +val LocalSettings = staticCompositionLocalOf { + error("No Settings provided") +} + val LocalSnackbarHostState = staticCompositionLocalOf { error("No SnackbarHostState provided") } @@ -79,6 +85,7 @@ fun App( profileCache: ProfileCache, accountRepository: AccountRepository, chatRepository: ChatRepository, + settingsRepository: SettingsRepository, ) { val viewModelFactory = remember { object : ViewModelProvider.Factory { @@ -92,6 +99,10 @@ fun App( accountRepository ) + modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel( + settingsRepository + ) + else -> throw IllegalArgumentException("Unknown ViewModel class") } @Suppress("UNCHECKED_CAST") @@ -102,6 +113,7 @@ fun App( val accountViewModel: AccountViewModel = viewModel(factory = viewModelFactory) val chatViewModel: ChatViewModel = viewModel(factory = viewModelFactory) + val settingsViewModel: SettingsViewModel = viewModel(factory = viewModelFactory) val context = LocalContext.current val activity = context as? ComponentActivity @@ -113,19 +125,24 @@ fun App( val accountState by accountViewModel.state.collectAsStateWithLifecycle() val signerRequired = accountState.signerRequired + // Get the settings + val settings by settingsViewModel.settings.collectAsStateWithLifecycle() + // Snackbar val snackbarHostState = remember { SnackbarHostState() } // Check if dark theme enabled - val darkMode = isSystemInDarkTheme() + val darkMode = when (settings.theme) { + Theme.Light -> false + Theme.Dark -> true + Theme.System -> isSystemInDarkTheme() + } // Enabled the dynamic color scheme val colorScheme = when { // Enable the dynamic color scheme for Android 12+ - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - if (isSystemInDarkTheme()) dynamicDarkColorScheme(context) else dynamicLightColorScheme( - context - ) + settings.dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (darkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } // When dark mode is enabled, use the dark color scheme darkMode -> darkColorScheme() @@ -190,11 +207,11 @@ fun App( ) { CompositionLocalProvider( LocalProfileCache provides profileCache, + LocalSettings provides settings, LocalSnackbarHostState provides snackbarHostState, LocalNavigator provides navigator, LocalScanResult provides qrScanResult, ) { - NavDisplay( backStack = backStack, onBack = { diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt index a50c8c2..a1745ef 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt @@ -14,6 +14,7 @@ import su.reya.coop.nostr.NostrManager import su.reya.coop.repository.AccountRepository import su.reya.coop.repository.ChatRepository import su.reya.coop.repository.MediaRepository +import su.reya.coop.repository.SettingsRepository import su.reya.coop.viewmodel.ProfileCache import kotlin.system.exitProcess @@ -25,16 +26,28 @@ class MainActivity : ComponentActivity() { private val profileCache by lazy { ProfileCache(NostrManager.instance) } private val scope = MainScope() + private val settingsRepository by lazy { + val storage = AppStore(this@MainActivity) + SettingsRepository(storage, scope) + } + private val accountRepository by lazy { val storage = AppStore(this@MainActivity) - val mediaRepository = MediaRepository() + val mediaRepository = MediaRepository(settingsRepository) val androidSigner = AndroidExternalSigner(this@MainActivity, externalSignerLauncher) - AccountRepository(NostrManager.instance, storage, mediaRepository, scope, androidSigner) + AccountRepository( + NostrManager.instance, + storage, + mediaRepository, + settingsRepository, + scope, + androidSigner + ) } private val chatRepository by lazy { - val mediaRepository = MediaRepository() - ChatRepository(NostrManager.instance, mediaRepository, scope) + val mediaRepository = MediaRepository(settingsRepository) + ChatRepository(NostrManager.instance, mediaRepository, settingsRepository, scope) } override fun onCreate(savedInstanceState: Bundle?) { @@ -82,6 +95,7 @@ class MainActivity : ComponentActivity() { profileCache = profileCache, accountRepository = accountRepository, chatRepository = chatRepository, + settingsRepository = settingsRepository, ) } } diff --git a/shared/src/commonMain/kotlin/su/reya/coop/repository/AccountRepository.kt b/shared/src/commonMain/kotlin/su/reya/coop/repository/AccountRepository.kt index c0f5cb9..b1d068c 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/repository/AccountRepository.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/repository/AccountRepository.kt @@ -57,6 +57,7 @@ class AccountRepository( private val nostr: Nostr, private val storage: AppStorage, private val mediaRepository: MediaRepository, + private val settingsRepository: SettingsRepository, private val scope: CoroutineScope, private val externalSignerHandler: ExternalSignerHandler? = null, private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, @@ -75,9 +76,7 @@ class AccountRepository( @OptIn(ExperimentalCoroutinesApi::class) val currentUserProfile: StateFlow = nostr.signer.publicKeyFlow - .flatMapLatest { pubkey -> - if (pubkey != null) currentUserProfileFlow(pubkey) else flowOf(null) - } + .flatMapLatest { if (it != null) currentUserProfileFlow(it) else flowOf(null) } .stateIn(scope, SharingStarted.WhileSubscribed(5000), null) init { diff --git a/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt b/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt index 5cbda77..2cabfe3 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt @@ -36,6 +36,7 @@ data class ChatState( class ChatRepository( private val nostr: Nostr, private val mediaRepository: MediaRepository, + private val settingsRepository: SettingsRepository, private val scope: CoroutineScope, private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, ) : ErrorHost by createErrorHost() { diff --git a/shared/src/commonMain/kotlin/su/reya/coop/repository/MediaRepository.kt b/shared/src/commonMain/kotlin/su/reya/coop/repository/MediaRepository.kt index 82380f4..a8c04e8 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/repository/MediaRepository.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/repository/MediaRepository.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.json.Json import rust.nostr.sdk.AsyncNostrSigner import su.reya.coop.blossom.BlossomClient -class MediaRepository { +class MediaRepository( + private val settingsRepository: SettingsRepository +) { private val httpClient = HttpClient { install(ContentNegotiation) { json(Json { @@ -25,12 +27,9 @@ class MediaRepository { contentType: String? = "image/jpeg" ): String? { return try { - val blossom = BlossomClient(url = "https://blossom.band", client = httpClient) - val descriptor = blossom.upload( - file = file, - contentType = contentType, - signer = signer, - ) + val url = settingsRepository.settings.value.blossomServer ?: "https://blossom.band" + val blossom = BlossomClient(url, httpClient) + val descriptor = blossom.upload(file = file, contentType = contentType, signer = signer) descriptor?.url } catch (e: CancellationException) { throw e diff --git a/shared/src/commonMain/kotlin/su/reya/coop/repository/SettingsRepository.kt b/shared/src/commonMain/kotlin/su/reya/coop/repository/SettingsRepository.kt index 693d0f6..3f51b3e 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/repository/SettingsRepository.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/repository/SettingsRepository.kt @@ -1,12 +1,19 @@ package su.reya.coop.repository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import su.reya.coop.AppStorage import su.reya.coop.Settings class SettingsRepository( - private val storage: AppStorage + private val storage: AppStorage, + private val scope: CoroutineScope ) { companion object { private const val KEY_SETTINGS = "app_settings" @@ -17,12 +24,29 @@ class SettingsRepository( encodeDefaults = true } - suspend fun save(settings: Settings) { + private val _settings = MutableStateFlow(Settings()) + val settings: StateFlow = _settings.asStateFlow() + + init { + scope.launch { + _settings.value = load() + } + } + + fun update(transform: (Settings) -> Settings) { + scope.launch { + val newSettings = transform(_settings.value) + _settings.value = newSettings + save(newSettings) + } + } + + private suspend fun save(settings: Settings) { val jsonString = json.encodeToString(settings) storage.set(KEY_SETTINGS, jsonString) } - suspend fun load(): Settings { + private suspend fun load(): Settings { val jsonString = storage.get(KEY_SETTINGS) return if (jsonString != null) { try { diff --git a/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/SettingsViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/SettingsViewModel.kt new file mode 100644 index 0000000..3d45a45 --- /dev/null +++ b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/SettingsViewModel.kt @@ -0,0 +1,16 @@ +package su.reya.coop.viewmodel + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.StateFlow +import su.reya.coop.Settings +import su.reya.coop.repository.SettingsRepository + +class SettingsViewModel( + private val repository: SettingsRepository +) : ViewModel() { + val settings: StateFlow = repository.settings + + fun update(transform: (Settings) -> Settings) { + repository.update(transform) + } +}