diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt index 19f70cb15a585942a8b8fcf3d5ebb707629c0b15..2cc9bf9b8513679022d353ba309a190f1097ea94 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt @@ -121,7 +121,6 @@ class App : Application(), RepositoryProvider, CoroutineScope by MainScope() + C scraper = HiwiTrackerScraper(portalClient) ) override val portalRepository = PortalRepository( - coroutineScope = this, portalClient, kaffeekasseRepository, hiwiTrackerRepository diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/CrashHandling.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/CrashHandling.kt index 9ca1598aeeea0ffbfb2552584d22a329a8ae996b..3aa371e5a28cd10e4a942e9bec9d7ad499682f98 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/CrashHandling.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/CrashHandling.kt @@ -3,6 +3,7 @@ package net.novagamestudios.kaffeekasse import android.app.Application import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.snapshotFlow import cafe.adriel.voyager.navigator.Navigator import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -63,7 +64,7 @@ object CrashHandling { } } launch { - portalRepository.session.collectLatest { + snapshotFlow { portalRepository.session.value }.collectLatest { ACRA.errorReporter.putCustomData("CurrentPortalSession", "$it") } } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt index 16c94081882eadabc6efbef0f6a540f0326428c1..2d9406c0cbd5accf0ebfbdabad9b10569dfd760f 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt @@ -1,6 +1,8 @@ package net.novagamestudios.kaffeekasse.api.portal +import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.contentnegotiation.ContentNegotiation @@ -25,8 +27,6 @@ import it.skrape.selects.Doc import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json @@ -76,7 +76,7 @@ class PortalClient( private val portal = object : Module(this, "portal") { - val currentSession = MutableStateFlow(Session()) + val currentSession = mutableStateOf(Session()) suspend fun updateSession(restore: String? = null): Unit = withContext(computationScope) { if (restore != null) { debug { "Restoring cookie: $restore" } @@ -104,7 +104,7 @@ class PortalClient( private val SessionCookieName = "PORTALSESSID" } - val session get() = portal.currentSession.asStateFlow() + val session: State<Session> get() = portal.currentSession data class Session( val response: PortalAPIResponse.Session = PortalAPIResponse.Session(), diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt index 88c87f012e9400503f686e1eb32c614ff998a59e..eeaf38176821b0a42de141293586eed8855a03a6 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt @@ -1,19 +1,21 @@ package net.novagamestudios.kaffeekasse.repositories +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import kotlinx.coroutines.flow.StateFlow -import net.novagamestudios.common_utils.core.collection.mapState import net.novagamestudios.common_utils.compose.state.MutableDataStoreState +import net.novagamestudios.common_utils.core.collection.mapState import net.novagamestudios.kaffeekasse.model.session.realUserOrNull class SettingsRepository( - repositoryProvider: RepositoryProvider, - settingsStore: MutableSettingsStore + private val repositoryProvider: RepositoryProvider, + private val settingsStore: MutableSettingsStore ) : MutableSettingsStore by settingsStore { - val userSettings: StateFlow<MutableDataStoreState<UserSettings>?> by lazy { - repositoryProvider.portalRepository.session.mapState { session -> - val key = session.realUserOrNull?.user ?: return@mapState null - UserSettingsStore(key, settingsStore) - } + private val userSettingsStores = mutableMapOf<String, UserSettingsStore>() + val userSettings: State<MutableDataStoreState<UserSettings>?> = derivedStateOf { + val session = repositoryProvider.portalRepository.session.value + val key = session.realUserOrNull?.user ?: return@derivedStateOf null + userSettingsStores.getOrPut(key) { UserSettingsStore(key, settingsStore) } } } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt index 80e492349013029e7696e38c69ac0e28a9107dc8..2de5eb9e9f1fa3e2d287af470353f227288889ee 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt @@ -1,5 +1,10 @@ package net.novagamestudios.kaffeekasse.repositories.i11 +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -42,8 +47,8 @@ class KaffeekasseRepository( ) : PortalRepositoryModule(), CoroutineScope by coroutineScope, Logger { private val client get() = api.client - private val mutableCurrentDevice = MutableStateFlow<Device?>(null) - internal val currentDevice = mutableCurrentDevice.asStateFlow() + private val mutableCurrentDevice = mutableStateOf<Device?>(null) + internal val currentDevice: State<Device?> get() = mutableCurrentDevice diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt index e2437e33f45d0f17acb088833f8d8be125a32748..0757e9e2f424c9a16e7ac4e2538768aadd47b681 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt @@ -1,9 +1,7 @@ package net.novagamestudios.kaffeekasse.repositories.i11 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import net.novagamestudios.common_utils.core.logging.Logger import net.novagamestudios.common_utils.core.logging.debug import net.novagamestudios.common_utils.core.logging.info @@ -17,7 +15,6 @@ import net.novagamestudios.kaffeekasse.model.session.realUserOrNull class PortalRepository( - coroutineScope: CoroutineScope, private val client: PortalClient, internal val kaffeekasse: KaffeekasseRepository, vararg otherModules: PortalRepositoryModule @@ -28,17 +25,16 @@ class PortalRepository( modules.forEach { it.portal = this } } - public override val session = combine( - client.session, - kaffeekasse.currentDevice - ) { session, device -> + public override val session: State<Session> = derivedStateOf { + val session = client.session.value + val device = kaffeekasse.currentDevice.value val response = session.response Session( device = device, user = if (response.isLoggedIn) User(response.username, response.displayName) else null, data = session.data ) - }.stateIn(coroutineScope, SharingStarted.Eagerly, Session.Empty) + } suspend fun forceUpdateSession(restore: String? = null) { info { "Updating session" } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt index 89145a53f320abe9b775e63a4cad77662415a53b..7f0061d45f5fe7402475986771b9ceb7aa20918a 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt @@ -23,7 +23,6 @@ import net.novagamestudios.common_utils.core.logging.LoggerForFun import net.novagamestudios.common_utils.core.logging.debug import net.novagamestudios.common_utils.voyager.model.GlobalScreenModelFactory import net.novagamestudios.common_utils.voyager.model.ScreenModelProvider -import net.novagamestudios.common_utils.voyager.model.collectAsStateHere import net.novagamestudios.common_utils.voyager.model.getValue import net.novagamestudios.common_utils.voyager.requireWithKey import net.novagamestudios.kaffeekasse.App @@ -40,7 +39,7 @@ class AppScreenModel private constructor( private val portal: PortalRepository ) : ScreenModel, Logger { - val session by portal.session.collectAsStateHere() + val session by portal.session companion object : GlobalScreenModelFactory<RepositoryProvider, App, AppScreenModel>, ScreenModelProvider<AppScreenModel> { context (RepositoryProvider) diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/AppModules.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/AppModules.kt index 31d362ac170dda6149654bbc2612c4f61d3de5aa..036699d251b42a56c2d10a857ec13eeb2f3dee6a 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/AppModules.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/AppModules.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import net.novagamestudios.common_utils.voyager.model.ScreenModelFactory -import net.novagamestudios.common_utils.voyager.model.collectAsStateHere import net.novagamestudios.common_utils.voyager.nearestScreen import net.novagamestudios.kaffeekasse.AppModule import net.novagamestudios.kaffeekasse.AppModules @@ -51,7 +50,7 @@ class AppModulesScreenModel private constructor( ) : ScreenModel { - private val userSettings by settingsRepository.userSettings.collectAsStateHere() + private val userSettings by settingsRepository.userSettings val modules: AppModules get() = when { settingsRepository.value.developerMode -> allModules session is Session.WithDevice -> AppModules(allModules.filterIsInstance<KaffeekasseModule>()) diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt index 86f16fe047ab59742f1d23c87fc186e759b8208a..78b7439aea78da344c31bdb9e4823b79bdbc2857 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt @@ -10,7 +10,6 @@ import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -20,8 +19,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.launch -import net.novagamestudios.common_utils.core.toastShort import net.novagamestudios.common_utils.compose.state.MutableDataStoreState +import net.novagamestudios.common_utils.core.toastShort +import net.novagamestudios.common_utils.voyager.BackNavigationHandler import net.novagamestudios.kaffeekasse.App.Companion.settings import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item @@ -32,7 +32,6 @@ import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.BasicCard import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryCard import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.LazyBasicCardGrid import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled -import net.novagamestudios.common_utils.voyager.BackNavigationHandler class CategorizedItemsState( items: Iterable<Item> @@ -179,7 +178,7 @@ private fun ItemCard( cart: MutableCart ) { val coroutineScope = rememberCoroutineScope() - val userSettings by settings().userSettings.collectAsState() + val userSettings by settings().userSettings val context = LocalContext.current net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.ItemCard( item = item, diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt index 6cdd090a0365270a29f899b611b9fcb4e5428be6..fdcc100d6b58f40369fbcc54552c84b27c7ee41c 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign @@ -31,10 +32,10 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import net.novagamestudios.common_utils.core.logging.Logger import net.novagamestudios.common_utils.compose.DashedShape import net.novagamestudios.common_utils.compose.components.ColumnCenter import net.novagamestudios.common_utils.compose.state.ReentrantActionState +import net.novagamestudios.common_utils.core.logging.Logger import net.novagamestudios.common_utils.core.logging.verbose import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart @@ -70,8 +71,14 @@ class CustomItemsState private constructor( .stateIn(computationScope, SharingStarted.Eagerly) @OptIn(ExperimentalCoroutinesApi::class) + private val currentUserFavoriteItemIds = snapshotFlow { settingsRepository.userSettings.value } + .flatMapLatest { it?.values ?: emptyFlow() } + .map { it.favoriteItemIds } + .distinctUntilChanged() + .asRichDataFlow() + val favoriteItems = combineRich( - settingsRepository.userSettings.flatMapLatest { it?.values ?: emptyFlow() }.map { it.favoriteItemIds }.distinctUntilChanged().asRichDataFlow(), + currentUserFavoriteItemIds, allItemsById ) { favoriteItems, itemsById -> favoriteItems.mapNotNull { itemsById[it] } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/FailureRetryScreen.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/FailureRetryScreen.kt index 1ecf4e1fcba07bfac956ff3db30584785d913417..2b9a844c0e3932c1395d0d43c67b7b20de7fadea 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/FailureRetryScreen.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/FailureRetryScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedIconButton +import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -63,7 +64,7 @@ fun FailureRetryScreen( Spacer(Modifier.height(8.dp)) var showDetailsDialog by remember { mutableStateOf(false) } TextButton(onClick = { showDetailsDialog = true }) { Text("Details") } - AlertDialog( + if (showDetailsDialog) AlertDialog( onDismissRequest = { showDetailsDialog = false }, confirmButton = { Button(onClick = { showDetailsDialog = false }) { Text("OK") } @@ -88,15 +89,17 @@ fun FailureRetryScreen( errorsShort = errorsShort, errorDetail = if (exceptions.isNotEmpty()) { { - Column( - Modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - exceptions.forEach { - DebugDataText(it.stackTraceToString()) - } - if (additionalDetails != null) { - DebugDataText(additionalDetails) + ProvideTextStyle(MaterialTheme.typography.bodySmall) { + Column( + Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + exceptions.forEach { + DebugDataText(it.stackTraceToString(),) + } + if (additionalDetails != null) { + DebugDataText(additionalDetails) + } } } } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt index 37864ed1fd50ce6874f077fa5cdc679feb541a4c..b2cb6d4778938f43e6172775c3b508cb094684c6 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -53,8 +52,8 @@ import coil.request.ImageRequest import io.ktor.http.URLBuilder import io.ktor.http.URLProtocol import net.novagamestudios.common_utils.compose.components.BoxCenter -import net.novagamestudios.common_utils.compose.maskedCircleIcon import net.novagamestudios.common_utils.compose.logging.LocalLogger +import net.novagamestudios.common_utils.compose.maskedCircleIcon import net.novagamestudios.common_utils.core.logging.info import net.novagamestudios.common_utils.core.logging.warn import net.novagamestudios.kaffeekasse.App @@ -64,6 +63,7 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction import net.novagamestudios.kaffeekasse.model.session.Session import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled +import net.novagamestudios.kaffeekasse.ui.util.derived import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichState @Composable @@ -179,10 +179,11 @@ private fun EstimatedItemPrice( item: Item, modifier: Modifier = Modifier ) { + // TODO Bad: Don't do logic in UI val app = app() - val session = app.portalRepository.session.collectAsState().value - if (session !is Session.WithRealUser) return - val transactionsState = remember { app.kaffeekasseRepository.transactions[session.realUser] }.collectAsRichState() + val user = app.portalRepository.session.derived { (this as? Session.WithRealUser)?.realUser }.value + if (user == null) return + val transactionsState = remember { app.kaffeekasseRepository.transactions[user] }.collectAsRichState() val lastUnitPrice by remember(transactionsState) { derivedStateOf { transactionsState.dataOrNull?.findLastUnitPrice(item) diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/Login.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/Login.kt index 2af11fecfc0ebf2b87f18181e266fdd7e91fdb79..5aaf42eec382a39241d87e60e3ab177d2c4a12c7 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/Login.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/Login.kt @@ -33,7 +33,7 @@ class LoginScreenModel private constructor( private val portal: PortalRepository ) : ScreenModel, Logger { - val session by portal.session.collectAsStateHere() + val session by portal.session val isLoading by loginRepository.isPerformingAction.collectAsStateHere() diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginForm.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginForm.kt index 5336c891f2901f69643a93eebc477049ee6a0f6b..d105db89f96eb630bb2bd54841894e2bfa3202f7 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginForm.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginForm.kt @@ -75,7 +75,7 @@ class LoginFormScreenModel private constructor( private val portal: PortalRepository ) : ScreenModel, Logger { - val session by portal.session.collectAsStateHere() + val session by portal.session val autoLogin by derivedStateOf { settings.value.autoLogin }