Compare commits
2 Commits
255c840847
...
6bb4ef2805
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bb4ef2805 | |||
| e4e73da229 |
@@ -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<ProfileCache> {
|
||||
error("No ProfileCache provided")
|
||||
}
|
||||
|
||||
val LocalSettings = staticCompositionLocalOf<Settings> {
|
||||
error("No Settings provided")
|
||||
}
|
||||
|
||||
val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
|
||||
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 = {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
22
shared/src/commonMain/kotlin/su/reya/coop/Settings.kt
Normal file
22
shared/src/commonMain/kotlin/su/reya/coop/Settings.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package su.reya.coop
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Settings(
|
||||
val theme: Theme = Theme.System,
|
||||
val dynamicColor: Boolean = true,
|
||||
val media: Media = Media.AlwaysEnabled,
|
||||
val screening: Boolean = true,
|
||||
val blossomServer: String? = "https://blossom.band",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class Theme {
|
||||
Light, Dark, System
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Media {
|
||||
Disabled, DisabledForMobileData, AlwaysEnabled
|
||||
}
|
||||
@@ -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<Profile?> = 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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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 scope: CoroutineScope
|
||||
) {
|
||||
companion object {
|
||||
private const val KEY_SETTINGS = "app_settings"
|
||||
}
|
||||
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
private val _settings = MutableStateFlow(Settings())
|
||||
val settings: StateFlow<Settings> = _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)
|
||||
}
|
||||
|
||||
private suspend fun load(): Settings {
|
||||
val jsonString = storage.get(KEY_SETTINGS)
|
||||
return if (jsonString != null) {
|
||||
try {
|
||||
json.decodeFromString<Settings>(jsonString)
|
||||
} catch (_: Exception) {
|
||||
Settings()
|
||||
}
|
||||
} else {
|
||||
Settings()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Settings> = repository.settings
|
||||
|
||||
fun update(transform: (Settings) -> Settings) {
|
||||
repository.update(transform)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user