mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 19:26:12 -04:00
refactor(ui): Replace waitForState and shareLatest with lifecycle-aware state collection
Add collectAsStateWithLifecycle for all Compose state collection (except Glance widgets). Add asLiveState() extension to ViewModel2 as a simpler replacement for shareLatest(scope = vmScope). Remove custom waitForState helper and shareLatest utility.
This commit is contained in:
@@ -24,8 +24,6 @@ PR titles appear in auto-generated changelogs and are read by users. Use **ELI5,
|
|||||||
| **General** | Dashboard, settings, notifications, themes, onboarding, support, app-wide UI |
|
| **General** | Dashboard, settings, notifications, themes, onboarding, support, app-wide UI |
|
||||||
| **Fix** | Bug fixes spanning multiple areas |
|
| **Fix** | Bug fixes spanning multiple areas |
|
||||||
|
|
||||||
For non-user-facing PRs (refactors, tests, CI, dependency bumps, translations), apply the `changelog-ignore` GitHub label. These don't need user-facing titles.
|
|
||||||
|
|
||||||
### Title Examples
|
### Title Examples
|
||||||
|
|
||||||
- `Widget: Add color themes and transparency slider`
|
- `Widget: Add color themes and transparency slider`
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package eu.darken.capod.common.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> waitForState(flow: Flow<T>): State<T?> {
|
|
||||||
return produceState(initialValue = null) {
|
|
||||||
flow.collect { value = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,12 @@ import androidx.activity.viewModels
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import eu.darken.capod.common.compose.waitForState
|
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.theming.CapodTheme
|
import eu.darken.capod.common.theming.CapodTheme
|
||||||
import eu.darken.capod.common.uix.Activity2
|
import eu.darken.capod.common.uix.Activity2
|
||||||
@@ -41,7 +40,7 @@ class RecorderActivity : Activity2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
|
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
|
||||||
CapodTheme(state = themeState) {
|
CapodTheme(state = themeState) {
|
||||||
val backgroundColor = MaterialTheme.colorScheme.background
|
val backgroundColor = MaterialTheme.colorScheme.background
|
||||||
val useDarkIcons = backgroundColor.luminance() > 0.5f
|
val useDarkIcons = backgroundColor.luminance() > 0.5f
|
||||||
@@ -61,7 +60,7 @@ class RecorderActivity : Activity2() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
RecorderScreen(
|
RecorderScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
@@ -14,41 +14,17 @@ import kotlinx.coroutines.flow.WhileSubscribed
|
|||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.scan
|
import kotlinx.coroutines.flow.scan
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a stateful flow, with the initial value of null, but never emits a null value.
|
|
||||||
* Helper method to create a new flow without suspending and without initial value
|
|
||||||
* The flow collector will just wait for the first value
|
|
||||||
*/
|
|
||||||
fun <T : Any> Flow<T>.shareLatest(
|
|
||||||
tag: String? = null,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0)
|
|
||||||
) = this
|
|
||||||
.onStart { if (tag != null) log(tag) { "shareLatest(...) start" } }
|
|
||||||
.onEach { if (tag != null) log(tag) { "shareLatest(...) emission: $it" } }
|
|
||||||
.onCompletion { if (tag != null) log(tag) { "shareLatest(...) completed." } }
|
|
||||||
.catch {
|
|
||||||
if (tag != null) log(tag) { "shareLatest(...) catch(): ${it.asLog()}" }
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
.stateIn(
|
|
||||||
scope = scope,
|
|
||||||
started = started,
|
|
||||||
initialValue = null
|
|
||||||
)
|
|
||||||
.filterNotNull()
|
|
||||||
|
|
||||||
fun <T : Any?> Flow<T>.replayingShare(scope: CoroutineScope) = this.shareIn(
|
fun <T : Any?> Flow<T>.replayingShare(scope: CoroutineScope) = this.shareIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import kotlinx.coroutines.CancellationException
|
|||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@@ -64,4 +68,12 @@ abstract class ViewModel2(
|
|||||||
|
|
||||||
open fun <T> Flow<T>.launchInViewModel() = this.launchIn(vmScope)
|
open fun <T> Flow<T>.launchInViewModel() = this.launchIn(vmScope)
|
||||||
|
|
||||||
|
fun <T : Any> Flow<T>.asLiveState(
|
||||||
|
started: SharingStarted = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
): Flow<T> = this.stateIn(
|
||||||
|
scope = vmScope,
|
||||||
|
started = started,
|
||||||
|
initialValue = null,
|
||||||
|
).filterNotNull()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,8 @@ import androidx.activity.enableEdgeToEdge
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
@@ -49,7 +49,7 @@ class MainActivity : Activity2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
|
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
|
||||||
val backStack = rememberNavBackStack(startDestination)
|
val backStack = rememberNavBackStack(startDestination)
|
||||||
navCtrl.setup(backStack)
|
navCtrl.setup(backStack)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import eu.darken.capod.R
|
|||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.common.permissions.Permission
|
import eu.darken.capod.common.permissions.Permission
|
||||||
@@ -129,11 +129,11 @@ fun OverviewScreenHost(vm: OverviewViewModel = hiltViewModel()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val stateHolder = waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
val state = stateHolder.value ?: return
|
val currentState = state ?: return
|
||||||
|
|
||||||
OverviewScreen(
|
OverviewScreen(
|
||||||
state = state,
|
state = currentState,
|
||||||
onRequestPermission = { vm.requestPermission(it) },
|
onRequestPermission = { vm.requestPermission(it) },
|
||||||
onManageDevices = { vm.goToDeviceManager() },
|
onManageDevices = { vm.goToDeviceManager() },
|
||||||
onSettings = { vm.goToSettings() },
|
onSettings = { vm.goToSettings() },
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import eu.darken.capod.common.debug.logging.log
|
|||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.SingleEventFlow
|
import eu.darken.capod.common.flow.SingleEventFlow
|
||||||
import eu.darken.capod.common.flow.combine
|
import eu.darken.capod.common.flow.combine
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.flow.throttleLatest
|
import eu.darken.capod.common.flow.throttleLatest
|
||||||
import eu.darken.capod.common.navigation.Nav
|
import eu.darken.capod.common.navigation.Nav
|
||||||
import eu.darken.capod.common.permissions.Permission
|
import eu.darken.capod.common.permissions.Permission
|
||||||
@@ -74,7 +74,7 @@ class OverviewViewModel @Inject constructor(
|
|||||||
monitorControl.startMonitor()
|
monitorControl.startMonitor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.shareLatest(scope = vmScope)
|
.asLiveState()
|
||||||
|
|
||||||
private val updateTicker = channelFlow<Unit> {
|
private val updateTicker = channelFlow<Unit> {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
@@ -113,7 +113,7 @@ class OverviewViewModel @Inject constructor(
|
|||||||
upgradeInfo = upgradeInfo,
|
upgradeInfo = upgradeInfo,
|
||||||
showUnmatchedDevices = showUnmatched,
|
showUnmatchedDevices = showUnmatched,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val now: Instant,
|
val now: Instant,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import eu.darken.capod.common.compose.Preview2
|
|||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.BuildConfigWrap
|
import eu.darken.capod.common.BuildConfigWrap
|
||||||
import eu.darken.capod.common.PrivacyPolicy
|
import eu.darken.capod.common.PrivacyPolicy
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.Nav
|
import eu.darken.capod.common.navigation.Nav
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
@@ -41,7 +41,7 @@ fun SettingsScreenHost(vm: SettingsViewModel = hiltViewModel()) {
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import eu.darken.capod.common.WebpageTool
|
import eu.darken.capod.common.WebpageTool
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -23,7 +23,7 @@ class SettingsViewModel @Inject constructor(
|
|||||||
|
|
||||||
val state = upgradeRepo.upgradeInfo
|
val state = upgradeRepo.upgradeInfo
|
||||||
.map { State(sponsorUrl = upgradeRepo.getSponsorUrl()) }
|
.map { State(sponsorUrl = upgradeRepo.getSponsorUrl()) }
|
||||||
.shareLatest(scope = vmScope)
|
.asLiveState()
|
||||||
|
|
||||||
fun openUrl(url: String) {
|
fun openUrl(url: String) {
|
||||||
webpageTool.open(url)
|
webpageTool.open(url)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import eu.darken.capod.R
|
|||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.bluetooth.ScannerMode
|
import eu.darken.capod.common.bluetooth.ScannerMode
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.common.settings.SettingsBaseItem
|
import eu.darken.capod.common.settings.SettingsBaseItem
|
||||||
@@ -74,7 +74,7 @@ fun GeneralSettingsScreenHost(vm: GeneralSettingsViewModel = hiltViewModel()) {
|
|||||||
vm.launchUpgradeFlow.collect { action -> activity?.let { action(it) } }
|
vm.launchUpgradeFlow.collect { action -> activity?.let { action(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
GeneralSettingsScreen(
|
GeneralSettingsScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
+2
-3
@@ -6,7 +6,6 @@ import eu.darken.capod.common.bluetooth.ScannerMode
|
|||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.SingleEventFlow
|
import eu.darken.capod.common.flow.SingleEventFlow
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.navigation.Nav
|
import eu.darken.capod.common.navigation.Nav
|
||||||
import eu.darken.capod.common.theming.ThemeColor
|
import eu.darken.capod.common.theming.ThemeColor
|
||||||
import eu.darken.capod.common.theming.ThemeMode
|
import eu.darken.capod.common.theming.ThemeMode
|
||||||
@@ -41,7 +40,7 @@ class GeneralSettingsViewModel @Inject constructor(
|
|||||||
val themeState: ThemeState,
|
val themeState: ThemeState,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.shareLatest(scope = vmScope)
|
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.asLiveState()
|
||||||
|
|
||||||
val launchUpgradeFlow = SingleEventFlow<(Activity) -> Unit>()
|
val launchUpgradeFlow = SingleEventFlow<(Activity) -> Unit>()
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ class GeneralSettingsViewModel @Inject constructor(
|
|||||||
useIndirectScanResultCallback = compat[2] as Boolean,
|
useIndirectScanResultCallback = compat[2] as Boolean,
|
||||||
themeState = themeState,
|
themeState = themeState,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
fun setMonitorMode(mode: MonitorMode) {
|
fun setMonitorMode(mode: MonitorMode) {
|
||||||
generalSettings.monitorMode.value = mode
|
generalSettings.monitorMode.value = mode
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.common.settings.SettingsBaseItem
|
import eu.darken.capod.common.settings.SettingsBaseItem
|
||||||
@@ -33,7 +33,7 @@ fun DebugSettingsScreenHost(vm: DebugSettingsViewModel = hiltViewModel()) {
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
DebugSettingsScreen(
|
DebugSettingsScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -31,7 +31,7 @@ class DebugSettingsViewModel @Inject constructor(
|
|||||||
showFakeData = fakeData,
|
showFakeData = fakeData,
|
||||||
showUnfiltered = unfiltered,
|
showUnfiltered = unfiltered,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
fun setDebugModeEnabled(enabled: Boolean) {
|
fun setDebugModeEnabled(enabled: Boolean) {
|
||||||
debugSettings.isDebugModeEnabled.value = enabled
|
debugSettings.isDebugModeEnabled.value = enabled
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import eu.darken.capod.common.PrivacyPolicy
|
|||||||
import eu.darken.capod.common.WebpageTool
|
import eu.darken.capod.common.WebpageTool
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
|
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
@@ -50,7 +50,7 @@ fun SupportScreenHost(vm: SupportViewModel = hiltViewModel()) {
|
|||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
|
|
||||||
var showShortRecordingWarning by remember { mutableStateOf(false) }
|
var showShortRecordingWarning by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -57,7 +57,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.WebpageTool
|
import eu.darken.capod.common.WebpageTool
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
|
import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
@@ -120,7 +120,7 @@ fun ContactFormScreenHost(vm: ContactFormViewModel = hiltViewModel()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
ContactFormScreen(
|
ContactFormScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import androidx.activity.enableEdgeToEdge
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
@@ -18,7 +18,6 @@ import androidx.glance.appwidget.GlanceAppWidgetManager
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.common.compose.waitForState
|
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.theming.CapodTheme
|
import eu.darken.capod.common.theming.CapodTheme
|
||||||
@@ -61,7 +60,7 @@ class WidgetConfigurationActivity : Activity2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val themeState by generalSettings.themeState.collectAsState(initial = generalSettings.currentThemeState)
|
val themeState by generalSettings.themeState.collectAsStateWithLifecycle(initialValue = generalSettings.currentThemeState)
|
||||||
CapodTheme(state = themeState) {
|
CapodTheme(state = themeState) {
|
||||||
val backgroundColor = MaterialTheme.colorScheme.background
|
val backgroundColor = MaterialTheme.colorScheme.background
|
||||||
val useDarkIcons = backgroundColor.luminance() > 0.5f
|
val useDarkIcons = backgroundColor.luminance() > 0.5f
|
||||||
@@ -72,7 +71,7 @@ class WidgetConfigurationActivity : Activity2() {
|
|||||||
insetsController.isAppearanceLightNavigationBars = useDarkIcons
|
insetsController.isAppearanceLightNavigationBars = useDarkIcons
|
||||||
}
|
}
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let { currentState ->
|
state?.let { currentState ->
|
||||||
WidgetConfigurationScreen(
|
WidgetConfigurationScreen(
|
||||||
state = currentState,
|
state = currentState,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import eu.darken.capod.common.coroutine.DispatcherProvider
|
|||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.combine
|
import eu.darken.capod.common.flow.combine
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.uix.ViewModel2
|
import eu.darken.capod.common.uix.ViewModel2
|
||||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||||
import eu.darken.capod.profiles.core.DeviceProfile
|
import eu.darken.capod.profiles.core.DeviceProfile
|
||||||
@@ -70,7 +69,7 @@ class WidgetConfigurationViewModel @Inject constructor(
|
|||||||
activePreset = activePreset,
|
activePreset = activePreset,
|
||||||
isCustomMode = activePreset == null,
|
isCustomMode = activePreset == null,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val profiles: List<DeviceProfile> = emptyList(),
|
val profiles: List<DeviceProfile> = emptyList(),
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import eu.darken.capod.R
|
|||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
@@ -50,7 +50,7 @@ fun DeviceManagerScreenHost(vm: DeviceManagerViewModel = hiltViewModel()) {
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
DeviceManagerScreen(
|
DeviceManagerScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.navigation.Nav
|
import eu.darken.capod.common.navigation.Nav
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
import eu.darken.capod.profiles.core.DeviceProfile
|
import eu.darken.capod.profiles.core.DeviceProfile
|
||||||
@@ -20,7 +20,7 @@ class DeviceManagerViewModel @Inject constructor(
|
|||||||
|
|
||||||
val state = deviceProfilesRepo.profiles.map { profiles ->
|
val state = deviceProfilesRepo.profiles.map { profiles ->
|
||||||
State(profiles = profiles)
|
State(profiles = profiles)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val profiles: List<DeviceProfile>,
|
val profiles: List<DeviceProfile>,
|
||||||
|
|||||||
+2
-2
@@ -61,7 +61,7 @@ import eu.darken.capod.R
|
|||||||
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.common.toHex
|
import eu.darken.capod.common.toHex
|
||||||
@@ -77,7 +77,7 @@ fun DeviceProfileCreationScreenHost(
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
|
|
||||||
var showUnsavedChangesDialog by rememberSaveable { mutableStateOf(false) }
|
var showUnsavedChangesDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
var showDeleteDialog by rememberSaveable { mutableStateOf(false) }
|
var showDeleteDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|||||||
+1
-2
@@ -11,7 +11,6 @@ import eu.darken.capod.common.coroutine.DispatcherProvider
|
|||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.SingleEventFlow
|
import eu.darken.capod.common.flow.SingleEventFlow
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.fromHex
|
import eu.darken.capod.common.fromHex
|
||||||
import eu.darken.capod.common.toHex
|
import eu.darken.capod.common.toHex
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
@@ -118,7 +117,7 @@ class DeviceProfileCreationViewModel @Inject constructor(
|
|||||||
minimumSignalQuality = editorState.minimumSignalQuality,
|
minimumSignalQuality = editorState.minimumSignalQuality,
|
||||||
canSave = formValid && hasChanges,
|
canSave = formValid && hasChanges,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val isEditMode: Boolean,
|
val isEditMode: Boolean,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
import eu.darken.capod.common.settings.SettingsBaseItem
|
import eu.darken.capod.common.settings.SettingsBaseItem
|
||||||
@@ -55,7 +55,7 @@ fun ReactionSettingsScreenHost(vm: ReactionSettingsViewModel = hiltViewModel())
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
val activity = LocalContext.current as Activity
|
val activity = LocalContext.current as Activity
|
||||||
|
|
||||||
state?.let {
|
state?.let {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.app.Activity
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||||
import eu.darken.capod.main.core.GeneralSettings
|
import eu.darken.capod.main.core.GeneralSettings
|
||||||
@@ -35,7 +35,7 @@ class ReactionSettingsViewModel @Inject constructor(
|
|||||||
val showPopUpOnConnection: Boolean,
|
val showPopUpOnConnection: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.shareLatest(scope = vmScope)
|
private val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.asLiveState()
|
||||||
|
|
||||||
val state = combine(
|
val state = combine(
|
||||||
isPro,
|
isPro,
|
||||||
@@ -57,7 +57,7 @@ class ReactionSettingsViewModel @Inject constructor(
|
|||||||
showPopUpOnCaseOpen = values[6] as Boolean,
|
showPopUpOnCaseOpen = values[6] as Boolean,
|
||||||
showPopUpOnConnection = values[7] as Boolean,
|
showPopUpOnConnection = values[7] as Boolean,
|
||||||
)
|
)
|
||||||
}.shareLatest(scope = vmScope)
|
}.asLiveState()
|
||||||
|
|
||||||
fun setOnePodMode(enabled: Boolean) {
|
fun setOnePodMode(enabled: Boolean) {
|
||||||
reactionSettings.onePodMode.value = enabled
|
reactionSettings.onePodMode.value = enabled
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.compose.Preview2
|
import eu.darken.capod.common.compose.Preview2
|
||||||
import eu.darken.capod.common.compose.PreviewWrapper
|
import eu.darken.capod.common.compose.PreviewWrapper
|
||||||
import eu.darken.capod.common.compose.waitForState
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import eu.darken.capod.common.error.ErrorEventHandler
|
import eu.darken.capod.common.error.ErrorEventHandler
|
||||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ fun TroubleShooterScreenHost(vm: TroubleShooterViewModel = hiltViewModel()) {
|
|||||||
ErrorEventHandler(vm)
|
ErrorEventHandler(vm)
|
||||||
NavigationEventHandler(vm)
|
NavigationEventHandler(vm)
|
||||||
|
|
||||||
val state by waitForState(vm.state)
|
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
|
||||||
state?.let {
|
state?.let {
|
||||||
TroubleShooterScreen(
|
TroubleShooterScreen(
|
||||||
state = it,
|
state = it,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import eu.darken.capod.common.debug.DebugSettings
|
|||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.flow.shareLatest
|
|
||||||
import eu.darken.capod.common.uix.ViewModel4
|
import eu.darken.capod.common.uix.ViewModel4
|
||||||
import eu.darken.capod.main.core.GeneralSettings
|
import eu.darken.capod.main.core.GeneralSettings
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
@@ -45,7 +45,7 @@ class TroubleShooterViewModel @Inject constructor(
|
|||||||
|
|
||||||
data class State(val bleState: BleState)
|
data class State(val bleState: BleState)
|
||||||
|
|
||||||
val state = _bleState.map { State(it) }.shareLatest(scope = vmScope)
|
val state = _bleState.map { State(it) }.asLiveState()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
_bleState
|
_bleState
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ fun DependencyHandlerScope.addCompose() {
|
|||||||
|
|
||||||
implementation("androidx.activity:activity-compose:1.12.1")
|
implementation("androidx.activity:activity-compose:1.12.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
|
||||||
|
|
||||||
implementation("androidx.hilt:hilt-navigation-compose:1.3.0-alpha01")
|
implementation("androidx.hilt:hilt-navigation-compose:1.3.0-alpha01")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user