diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fe63bb677dc7c018519fa0fb0fecb445e5256c67..f8467b458e43862c587a34f34c06af8fbbf30d0f 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="KotlinJpsPluginSettings"> - <option name="version" value="1.9.23" /> + <option name="version" value="1.9.10" /> </component> </project> \ No newline at end of file diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt index 996f2fa36017a60a000129d7cc4ee80a5a0db430..2bfe4619527be652f9e818c959a269ebcc0ce8d9 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt @@ -19,22 +19,24 @@ import kotlinx.coroutines.plus import net.novagamestudios.common_utils.Logger import net.novagamestudios.common_utils.compose.application import net.novagamestudios.common_utils.compose.state.loadInitialBlocking -import net.novagamestudios.common_utils.debug +import net.novagamestudios.common_utils.error import net.novagamestudios.common_utils.info import net.novagamestudios.common_utils.toastShort -import net.novagamestudios.kaffeekasse.api.portal.PortalClient +import net.novagamestudios.common_utils.warn import net.novagamestudios.kaffeekasse.api.hiwi_tracker.HiwiTrackerAPI import net.novagamestudios.kaffeekasse.api.hiwi_tracker.HiwiTrackerScraper import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseAPI import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseScraper -import net.novagamestudios.kaffeekasse.repositories.UpdateController +import net.novagamestudios.kaffeekasse.api.portal.PortalClient import net.novagamestudios.kaffeekasse.gitlab.GitLab -import net.novagamestudios.kaffeekasse.repositories.releases.GitLabReleases +import net.novagamestudios.kaffeekasse.repositories.Credentials +import net.novagamestudios.kaffeekasse.repositories.SettingsRepository +import net.novagamestudios.kaffeekasse.repositories.UpdateController import net.novagamestudios.kaffeekasse.repositories.i11.HiwiTrackerRepository import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository -import net.novagamestudios.kaffeekasse.repositories.Credentials import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository import net.novagamestudios.kaffeekasse.repositories.newSettingsStore +import net.novagamestudios.kaffeekasse.repositories.releases.GitLabReleases class App : Application(), CoroutineScope by MainScope() + CoroutineName(App::class.simpleName!!), Logger { @@ -43,22 +45,23 @@ class App : Application(), CoroutineScope by MainScope() + CoroutineName(App::cl super.onCreate() instanceOrNull = this info { "App instance available" } - settingsStore.loadInitialBlocking() + try { + settingsRepository.loadInitialBlocking() + } catch (e: Throwable) { + error(e) { "Failed to load initial settings" } + toastShort("Failed to load settings: ${e.message}") + throw e + } launch { try { releases.fetchNewerReleases() } catch (e: Throwable) { - debug(e) { "Failed to fetch newer releases" } + warn(e) { "Failed to fetch newer releases" } toastShort("Failed to check for updates: ${e.message}") } } -// ScreenRegistry { -// modules.forEach { it.applyScreenModule() } -// } } - - companion object : Logger { var instanceOrNull: App? = null private set @@ -80,15 +83,27 @@ class App : Application(), CoroutineScope by MainScope() + CoroutineName(App::cl ): T = with(app()) { LocalNavigator.currentOrThrow.rememberNavigatorScreenModel { factory() } } + + @Composable + fun settings(): SettingsRepository = app().settingsRepository + + val developerMode: Boolean @Composable get() = settings().value.developerMode } - val settingsStore = newSettingsStore(this) + + // Settings stuff + val settingsRepository = SettingsRepository( + app = this, + settingsStore = newSettingsStore(this) + ) val credentials by lazy { Credentials( credentialManager = CredentialManager.create(this), - settingsStore = settingsStore + settingsStore = settingsRepository ) } + + // Update stuff val gitLab = GitLab( instanceUrl = "https://git.rwth-aachen.de", projectPath = "jonas.broeckmann/kaffeekasse", @@ -96,6 +111,8 @@ class App : Application(), CoroutineScope by MainScope() + CoroutineName(App::cl ) val releases = GitLabReleases(gitLab) val updateController = UpdateController(this) + + // I11 stuff private val portalClient = PortalClient(this) val kaffeekasseRepository = KaffeekasseRepository( coroutineScope = this, @@ -113,6 +130,7 @@ class App : Application(), CoroutineScope by MainScope() + CoroutineName(App::cl hiwiTrackerRepository ) + // Active app modules val modules = AppModules( KaffeekasseModule, HiwiTrackerModule diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/MainActivity.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/MainActivity.kt index 798eca549c96192f9e135514e7a5e895d80a65a1..496c2bc48ae75d025dc0223a5abcd8a194d1db9c 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/MainActivity.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/MainActivity.kt @@ -3,24 +3,12 @@ package net.novagamestudios.kaffeekasse import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.lifecycle.ViewModelStoreOwner -import androidx.lifecycle.viewmodel.CreationExtras -import androidx.lifecycle.viewmodel.MutableCreationExtras -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner -import androidx.lifecycle.viewmodel.compose.viewModel import net.novagamestudios.common_utils.LocalLogger import net.novagamestudios.common_utils.Logger import net.novagamestudios.common_utils.error -import net.novagamestudios.common_utils.info -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore import net.novagamestudios.kaffeekasse.ui.App -import net.novagamestudios.kaffeekasse.ui.AppViewModel import net.novagamestudios.kaffeekasse.ui.theme.KaffeekasseTheme -import kotlin.time.measureTimedValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -29,8 +17,7 @@ class MainActivity : ComponentActivity() { setContent { val app = app() CompositionLocalProvider( - LocalLogger provides app, - LocalSettingsStore provides app.settingsStore + LocalLogger provides app ) { KaffeekasseTheme { App() diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt index 8611ae09f30bae6d02326488eaa8293359b8adec..821d6685d1e493cccce722fd3700740b0851b48c 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt @@ -3,7 +3,6 @@ package net.novagamestudios.kaffeekasse.repositories import android.content.Context import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.staticCompositionLocalOf import androidx.datastore.core.MultiProcessDataStoreFactory import kotlinx.coroutines.CoroutineScope import kotlinx.serialization.ExperimentalSerializationApi @@ -14,7 +13,6 @@ import kotlinx.serialization.serializer import net.novagamestudios.common_utils.JsonToDataStore import net.novagamestudios.common_utils.compose.state.DataStoreState import net.novagamestudios.common_utils.compose.state.MutableDataStoreState -import net.novagamestudios.common_utils.compose.state.rememberMockedDataStoreState import net.novagamestudios.common_utils.compose.state.stateIn import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials import java.io.File @@ -23,12 +21,9 @@ import java.io.File data class Settings( val themeMode: ThemeMode = ThemeMode.Dark, val autoLogin: Boolean = false, - @Deprecated("Use favoriteItemIds instead", ReplaceWith("favoriteItemIds")) - val favoriteItems: List<String> = emptyList(), - val favoriteItemIds: List<Int> = favoriteItems.mapNotNull { it.toIntOrNull() }, - val lastSelectedModule: String? = null, val deviceCredentials: DeviceCredentials? = null, - val developerMode: Boolean = false + val developerMode: Boolean = false, + val userSettings: Map<String, UserSettings> = emptyMap() ) { enum class ThemeMode { @@ -42,24 +37,24 @@ data class Settings( ThemeMode.Light -> false } - internal val serializersModule = SerializersModule { - - } + internal val serializersModule = SerializersModule { } + } +} +@Serializable +data class UserSettings( + val favoriteItemIds: List<Int> = emptyList(), + val lastSelectedModule: String? = null +) { + companion object { + val Empty = UserSettings() } } + typealias MutableSettingsStore = MutableDataStoreState<Settings> typealias SettingsStore = DataStoreState<Settings> -val LocalSettingsStore = staticCompositionLocalOf<MutableSettingsStore> { - throw NoSuchElementException() -} - -private val settingsValidator: Settings.(Settings?) -> Settings = { - this -} - @OptIn(ExperimentalSerializationApi::class) fun Context.newSettingsStore( coroutineScope: CoroutineScope @@ -79,8 +74,6 @@ fun Context.newSettingsStore( } ).stateIn(coroutineScope, settingsValidator) -@Composable -fun rememberMockedSettingsStore(initial: Settings = Settings()) = rememberMockedDataStoreState( - initial = initial, - validator = settingsValidator -) +private val settingsValidator: Settings.(Settings?) -> Settings = { + this +} diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..528a8d6d971e0bfa3b71c28aa611d1924fa56d61 --- /dev/null +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/SettingsRepository.kt @@ -0,0 +1,46 @@ +package net.novagamestudios.kaffeekasse.repositories + +import kotlinx.coroutines.flow.StateFlow +import net.novagamestudios.common_utils.compose.state.MutableDataStoreState +import net.novagamestudios.kaffeekasse.App +import net.novagamestudios.kaffeekasse.util.mapState + +class SettingsRepository( + app: App, + settingsStore: MutableSettingsStore +) : MutableSettingsStore by settingsStore { + val userSettings: StateFlow<MutableDataStoreState<UserSettings>?> by lazy { + app.portalRepository.currentUser.mapState { user -> + if (user == null) return@mapState null + val key = user.user ?: return@mapState null + UserSettingsStore(key, settingsStore) + } + } +} + + +private class UserSettingsStore( + private val userKey: String, + private val settingsStore: MutableSettingsStore +) : MutableDataStoreState<UserSettings> { + private fun Settings.map() = userSettings.getOrElse(userKey) { UserSettings.Empty } + private fun Updater<UserSettings>.wrapped(): Updater<Settings> = { settings -> + val new = this(settings.map()) + if (new == UserSettings.Empty) { + settings.copy(userSettings = settings.userSettings - userKey) + } else { + settings.copy(userSettings = settings.userSettings + (userKey to new)) + } + } + + override val value: UserSettings get() = settingsStore.value.map() + override val values: StateFlow<UserSettings> get() = settingsStore.values.mapState { settings -> settings.map() } + + override suspend fun loadInitial() = throw UnsupportedOperationException() + override fun provideInitial(value: UserSettings) = throw UnsupportedOperationException() + + override fun tryUpdate(updater: suspend (UserSettings) -> UserSettings) = settingsStore.tryUpdate(updater.wrapped()) + override suspend fun update(updater: suspend (UserSettings) -> UserSettings) = settingsStore.update(updater.wrapped()) +} + +private typealias Updater<T> = suspend (T) -> T 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 ee38cc77f1bd64b83f2fd1db5f4f02b7f850523d..1587110a15e26149b55e5a5bc2148c07ec157930 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 @@ -185,11 +185,12 @@ class KaffeekasseRepository( requireLoggedInUser() requireLoggedInDevice() cart.forEach { (item, count) -> - api.purchase( + val result = api.purchase( itemId = item.id, count = count, targetUserId = targetAccount?.id - ) + ).mapToRichDataState { this } + if (result is RichData.Error) throw IllegalStateException(result.messages.joinToString("\n")) } markBalanceDataDirty() } 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 48a307d23cfce72aa14392b2aae8e85d8bd07b14..2c037a64794f1fae4a04f1362ddc30628e32ca53 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt @@ -69,7 +69,7 @@ import net.novagamestudios.kaffeekasse.AppModules import net.novagamestudios.kaffeekasse.HiwiTrackerModule import net.novagamestudios.kaffeekasse.KaffeekasseModule import net.novagamestudios.kaffeekasse.model.credentials.isUser -import net.novagamestudios.kaffeekasse.repositories.MutableSettingsStore +import net.novagamestudios.kaffeekasse.repositories.SettingsRepository import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository import net.novagamestudios.kaffeekasse.ui.hiwi_tracker.HiwiTrackerTopBarActions import net.novagamestudios.kaffeekasse.ui.hiwi_tracker.HiwiTrackerTopBarTitle @@ -80,7 +80,7 @@ import net.novagamestudios.kaffeekasse.util.collectAsStateHere class AppViewModel private constructor( - val mutableSettingsStore: MutableSettingsStore, + val settingsRepository: SettingsRepository, private val portal: PortalRepository, private val allModules: AppModules ) : ScreenModel, Logger { @@ -91,14 +91,16 @@ class AppViewModel private constructor( val modules: AppModules get() { return when { - mutableSettingsStore.value.developerMode -> allModules + settingsRepository.value.developerMode -> allModules currentDevice != null -> AppModules(allModules.filter { it == KaffeekasseModule }) else -> allModules } } + val userSettings by settingsRepository.userSettings.collectAsStateHere() + val initialModule get() = modules - .find { it.id == mutableSettingsStore.value.lastSelectedModule } + .find { it.id == userSettings?.value?.lastSelectedModule } ?: modules.first() fun Tab.moduleOrNull() = modules.singleOrNull { it.navigationTab == this } @@ -106,7 +108,7 @@ class AppViewModel private constructor( companion object { @Composable fun vm() = App.globalScreenModel { AppViewModel( - mutableSettingsStore = settingsStore, + settingsRepository = settingsRepository, portal = portalRepository, allModules = modules ) @@ -143,7 +145,7 @@ fun App( } LaunchedEffect(tabNavigator.current) { val module = with(vm) { tabNavigator.current.moduleOrNull() } - if (module != null) vm.mutableSettingsStore.tryUpdate { + if (module != null) vm.userSettings?.tryUpdate { it.copy(lastSelectedModule = module.id) } } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt index fa95cf1f92e2b7604913942c21e0e3969d7199cb..d7ff037a80c25fb9488d95b2f49f1706abbb2f81 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt @@ -272,7 +272,7 @@ class LoginViewModel private constructor( companion object { @Composable fun vm() = net.novagamestudios.kaffeekasse.App.navigatorScreenModel { LoginViewModel( - mutableSettingsStore = settingsStore, + mutableSettingsStore = settingsRepository, credentials = credentials, portal = portalRepository, kaffeekasse = kaffeekasseRepository diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt index 118ee55746a08738bfbd5bbd01f219b2b19b8cf7..62b2bceb0d9abb07ffabfa59770995073f2b0ee4 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -21,7 +22,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Switch @@ -51,6 +51,7 @@ import net.novagamestudios.common_utils.compose.components.CircularLoadingBox import net.novagamestudios.common_utils.compose.components.ColumnCenter import net.novagamestudios.common_utils.compose.components.LinearProgressIndicator import net.novagamestudios.common_utils.compose.components.RowCenter +import net.novagamestudios.common_utils.compose.components.TransparentListItem import net.novagamestudios.common_utils.compose.state.ReentrantActionState import net.novagamestudios.common_utils.compose.state.collectAsStateIn import net.novagamestudios.common_utils.toastLong @@ -60,9 +61,8 @@ import net.novagamestudios.kaffeekasse.model.app.AppRelease import net.novagamestudios.kaffeekasse.model.app.AppVersion import net.novagamestudios.kaffeekasse.model.date_time.format import net.novagamestudios.kaffeekasse.repositories.InstallStatus -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore -import net.novagamestudios.kaffeekasse.repositories.releases.Releases import net.novagamestudios.kaffeekasse.repositories.UpdateController +import net.novagamestudios.kaffeekasse.repositories.releases.Releases import net.novagamestudios.kaffeekasse.util.openInBrowser import java.time.format.DateTimeFormatter @@ -292,6 +292,7 @@ private fun AppInfoDialog( } } }, + modifier = Modifier.widthIn(max = 400.dp), icon = { Icon(Icons.Default.Info, null) }, title = { Text("Kaffeekasse") }, text = { @@ -319,12 +320,12 @@ private fun AppInfoDialog( Spacer(Modifier.height(16.dp)) Text("Made with love\nby\nJonas Broeckmann", textAlign = TextAlign.Center) Spacer(Modifier.height(16.dp)) - val settingsStore = LocalSettingsStore.current - ListItem( + val settings = App.settings() + TransparentListItem( headlineContent = { Text("Developer mode") }, trailingContent = { Switch( - settingsStore.value.developerMode, - onCheckedChange = { new -> settingsStore.tryUpdate { it.copy(developerMode = new) } } + settings.value.developerMode, + onCheckedChange = { new -> settings.tryUpdate { it.copy(developerMode = new) } } ) } ) Spacer(Modifier.height(16.dp)) diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt index 817ea05f2beda9e03fcb831cae0ee3545d2ab2f5..578d1ea0239c45db5daab4e534afc4919f57c428 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt @@ -1,13 +1,16 @@ package net.novagamestudios.kaffeekasse.ui +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Lock @@ -140,18 +143,19 @@ fun UserSelection( .fillMaxWidth() ) }, dataContent = { users -> - Column( - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 300.dp), + Modifier.fillMaxSize(), + contentPadding = PaddingValues(8.dp), + horizontalArrangement = Arrangement.Center ) { - users.forEach { user -> + items(users) { user -> UserItem( user = user, onClick = { vm.selectUser(user) }, - Modifier.padding(6.dp) + Modifier + .padding(6.dp) + .widthIn(max = 300.dp) ) } } @@ -169,7 +173,7 @@ private fun UserItem( ) { OutlinedCard( onClick = onClick, - modifier.widthIn(max = 300.dp) + modifier ) { RowCenter(Modifier.padding(14.dp)) { Text("${user.lastName}, ${user.firstName}") diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/HiwiTrackerModule.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/HiwiTrackerModule.kt index 11423cd4153ad4a1ea5c2836f192e080525f70c3..3d88d6e3ed0b44f831bddc7ca4d1e8e63764734f 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/HiwiTrackerModule.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/HiwiTrackerModule.kt @@ -200,7 +200,7 @@ class HiwiTrackerModuleViewModel private constructor( companion object { @Composable fun vm() = App.navigatorScreenModel { HiwiTrackerModuleViewModel( - mutableSettingsStore = settingsStore, + mutableSettingsStore = settingsRepository, hiwiTracker = hiwiTrackerRepository ) } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt index 84250d1e8a81d5d0639d693499339f725f1ee958..da56a17e6d32ad1a411ab77e8ab08332cc2a8f45 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt @@ -38,7 +38,11 @@ class KaffeekasseModuleViewModel private constructor( private val currentDevice by portal.kaffeekasse.currentDevice.collectAsStateHere() private val currentUser by portal.currentUser.collectAsStateHere() - val accountName get() = if (currentDevice != null) currentUser?.displayName else null + val accountName get() = if (currentDevice != null) { + currentUser?.displayName ?: "Unknown User" + } else { + null + } companion object { @Composable fun vm() = App.navigatorScreenModel { diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Transactions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Transactions.kt index fdbb6c46fb777d9a775ad59be1eef13506f1f5c8..cf063c7b61f4ed1c1b11884dae5aec9904b4da02 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Transactions.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Transactions.kt @@ -47,7 +47,6 @@ import net.novagamestudios.kaffeekasse.data.category import net.novagamestudios.kaffeekasse.data.cleanFullName import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.FailureRetryScreen import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryIcon import net.novagamestudios.kaffeekasse.ui.util.PullToRefreshBox @@ -64,7 +63,7 @@ class TransactionsViewModel private constructor( val transactions get() = transactionsState.dataOrNull val errors get() = transactionsState.errorOrNull?.messages val isLoading get() = transactionsState.isLoading - val chartViewModel = TransactionsChartsViewModel(screenModelScope, snapshotFlow { transactions }) + val chartViewModel by lazy { TransactionsChartsViewModel(screenModelScope, snapshotFlow { transactions }) } var showCharts by mutableStateOf(false) @@ -215,7 +214,6 @@ private fun TransactionListItem( modifier: Modifier = Modifier ) = ListItem( headlineContent = { - val settings by LocalSettingsStore.current val style = LocalTextStyle.current when (val purpose = transaction.purpose) { is Transaction.Purpose.Purchase -> Row(verticalAlignment = Alignment.CenterVertically) { @@ -232,7 +230,7 @@ private fun TransactionListItem( } else { Text(knownItems.joinToString("/") { it.cleanFullName }, style = style) } - if (settings.developerMode) ProvideTextStyle(MaterialTheme.typography.labelSmall) { + if (App.developerMode) ProvideTextStyle(MaterialTheme.typography.labelSmall) { if (knownItems.isEmpty()) Text( "Unknown", Modifier.padding(start = 4.dp), 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 79b74d537cd5df425f684c882b20854a15859fbb..9300550eef5f8cc9b49811e214d5f8b6deb92700 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 @@ -9,6 +9,7 @@ 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 @@ -18,12 +19,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.launch import net.novagamestudios.common_utils.toastShort +import net.novagamestudios.kaffeekasse.App import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item import net.novagamestudios.kaffeekasse.model.kaffeekasse.ItemCategory -import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.BasicCardGrid import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryCard import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.LazyBasicCardGrid @@ -152,7 +152,7 @@ private fun ItemCard( cart: MutableCart ) { val coroutineScope = rememberCoroutineScope() - val settingsStore = LocalSettingsStore.current + val userSettings by App.settings().userSettings.collectAsState() val context = LocalContext.current net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.ItemCard( item = item, @@ -166,7 +166,7 @@ private fun ItemCard( onRemove = { cart -= item }, onLongClick = { coroutineScope.launch { - settingsStore.update { + userSettings?.update { val list = it.favoriteItemIds.toMutableList() val wasFavorite = item.id in list if (wasFavorite) list -= item.id diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt index d6f5033d1c153f92b350b3e7ca412f883d0bb341..f8eb04fc079f6df7dbbf684a8fb7060b8e296b21 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt @@ -113,7 +113,7 @@ abstract class CheckoutViewModel( true } catch (e: Exception) { warn(e) { "Failed to submit cart" } - errorToast = "Fehler: ${e.message ?: "Unbekannter Fehler"}" + errorToast = "Fehler: ${e.message ?: e::class.simpleName ?: "Unbekannter Fehler"}" false } } @@ -177,6 +177,8 @@ class APICheckoutViewModel( selfUserExtended, kaffeekasse.basicUserInfoList ) { self, userList -> + // FIXME wrong way around + val fromWhitelist = self.whitelist.orEmpty() val fromBlacklist = self.blacklist?.let { blacklist -> val blacklistIds = blacklist.mapTo(mutableSetOf()) { it.id } 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 5048dcc67f6c0899b6ec09f9d286faa3ab3fdec1..645d7e5c3c846c3431189b4b8e09aba421ff476f 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 @@ -22,10 +22,13 @@ import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -38,8 +41,8 @@ import net.novagamestudios.kaffeekasse.App import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction +import net.novagamestudios.kaffeekasse.repositories.SettingsRepository import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository -import net.novagamestudios.kaffeekasse.repositories.SettingsStore import net.novagamestudios.kaffeekasse.ui.theme.disabled import net.novagamestudios.kaffeekasse.util.richdata.asRichDataFlow import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateHere @@ -50,7 +53,7 @@ import net.novagamestudios.kaffeekasse.util.richdata.stateIn import java.time.LocalDateTime class CustomItemsViewModel private constructor( - settingsStore: SettingsStore, + settingsRepository: SettingsRepository, private val kaffeekasse: KaffeekasseRepository ) : ScreenModel, Logger { @@ -64,9 +67,9 @@ class CustomItemsViewModel private constructor( .mapRich { items -> items.associateBy { it.originalName } } .stateIn(screenModelScope, SharingStarted.Eagerly) -// val favoriteItems by derivedStateOf { settings.favoriteItems.mapNotNull { item -> allItemsById[item] } } + @OptIn(ExperimentalCoroutinesApi::class) val favoriteItems = combineRich( - settingsStore.values.map { it.favoriteItemIds }.distinctUntilChanged().asRichDataFlow(), + settingsRepository.userSettings.flatMapLatest { it?.values ?: emptyFlow() }.map { it.favoriteItemIds }.distinctUntilChanged().asRichDataFlow(), allItemsById ) { favoriteItems, itemsById -> favoriteItems.mapNotNull { itemsById[it] } @@ -172,7 +175,7 @@ class CustomItemsViewModel private constructor( @Composable fun vm() = App.navigatorScreenModel { CustomItemsViewModel( - settingsStore = settingsStore, + settingsRepository = settingsRepository, kaffeekasse = kaffeekasseRepository ) } 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 4d9cf4a7b041ef70ea7d105b6e4f1478d193bad6..28dbaeecbca62c4e42d5ad9bb48547145443b750 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 @@ -56,11 +56,11 @@ import net.novagamestudios.common_utils.compose.components.BoxCenter import net.novagamestudios.common_utils.compose.maskedCircleIcon import net.novagamestudios.common_utils.info import net.novagamestudios.common_utils.warn +import net.novagamestudios.kaffeekasse.App import net.novagamestudios.kaffeekasse.app import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichState @Composable @@ -82,7 +82,6 @@ fun ItemCard( ), highlighted = highlighted ) { - val settings by LocalSettingsStore.current BoxCenter( Modifier .weight(1f) @@ -113,7 +112,7 @@ fun ItemCard( onRemove = onRemove ) } - if (settings.developerMode) ProvideTextStyle(MaterialTheme.typography.labelSmall) { + if (App.developerMode) ProvideTextStyle(MaterialTheme.typography.labelSmall) { Row { Text("${item.id}") Spacer(Modifier.weight(1f)) @@ -144,11 +143,10 @@ private fun ItemInformation( ) } Spacer(Modifier.weight(1f)) - val settings by LocalSettingsStore.current val transactionsState = app().kaffeekasseRepository.transactions.collectAsRichState() val lastUnitPrice by remember { derivedStateOf { transactionsState.dataOrNull?.findLastUnitPrice(item) } } (item.price ?: lastUnitPrice ?: item.estimatedPrice)?.let { - val highlighted = it != item.estimatedPrice && settings.developerMode + val highlighted = it != item.estimatedPrice && App.developerMode Text( remember(it) { listOfNotNull( diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt index c8e84fc9bd65d404587440f0992ac1be60e8f848..acdbf9c1e17d0bee0bd501e20f0f1ea18db3aa7b 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore +import net.novagamestudios.kaffeekasse.App import net.novagamestudios.kaffeekasse.repositories.Settings.Companion.isDarkMode private val DarkColorScheme = darkColorScheme( @@ -31,7 +31,7 @@ private val LightColorScheme = lightColorScheme( @Composable fun KaffeekasseTheme( - darkTheme: Boolean = LocalSettingsStore.current.value.isDarkMode, + darkTheme: Boolean = App.settings().value.isDarkMode, // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/KaffeekassePreview.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/KaffeekassePreview.kt deleted file mode 100644 index f7737c8df4178fe3e940c42e05f90f0cb67f866f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/KaffeekassePreview.kt +++ /dev/null @@ -1,41 +0,0 @@ -package net.novagamestudios.kaffeekasse.ui.util - -import android.annotation.SuppressLint -import android.content.res.Configuration -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.tooling.preview.Preview -import net.novagamestudios.kaffeekasse.repositories.LocalSettingsStore -import net.novagamestudios.kaffeekasse.repositories.Settings -import net.novagamestudios.kaffeekasse.repositories.rememberMockedSettingsStore -import net.novagamestudios.kaffeekasse.ui.theme.KaffeekasseTheme - - - -@Preview( - showBackground = false, - showSystemUi = true, - device = "id:pixel_3a", - uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL -) -annotation class PreviewDevice - -@Preview( - showBackground = false, - showSystemUi = false, - uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL -) -annotation class PreviewMinimal - - -@SuppressLint("RememberReturnType") -@Composable -fun PreviewTheme( - initialSettings: Settings = Settings(), - content: @Composable () -> Unit -) = CompositionLocalProvider( - LocalSettingsStore provides rememberMockedSettingsStore(initialSettings) -) { - KaffeekasseTheme(content = content) -} - diff --git a/settings.gradle.kts b/settings.gradle.kts index 84e0f446984555ebf00037472fd7f2714c3d07b4..1ca878fe428515bda6b02176aa3f37698afce977 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,12 +15,12 @@ dependencyResolutionManagement { } versionCatalogs { create("libs") { - version("kotlin", "1.9.23") - version("compose-compiler", "1.5.12") + version("kotlin", "1.9.10") + version("compose-compiler", "1.5.3") version("compose-bom", "2024.04.01") version("material3", "1.3.0-alpha05") version("androidx-lifecycle", "2.8.0-beta01") - version("ktor", "3.0.0-beta-1") + version("ktor", "2.3.8") version("vico", "2.0.0-alpha.8") version("voyager", "1.1.0-alpha04") version("coil", "2.6.0")