diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b589d56e9f285d8cfdc6c270853a5d439021a278..e58d3e423fd6ba1f41f8b449410b7a5c9b2a5695 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="17" />
+    <bytecodeTargetLevel target="19" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0897082f7512e48e89310db81b5455d997417505..8f1b960d1647c16b49204dafeda373864f7f7c7c 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -5,7 +5,7 @@
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
-        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
+        <option name="gradleJvm" value="19" />
         <option name="modules">
           <set>
             <option value="$PROJECT_DIR$" />
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index f8467b458e43862c587a34f34c06af8fbbf30d0f..fe63bb677dc7c018519fa0fb0fecb445e5256c67 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.10" />
+    <option name="version" value="1.9.23" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 2188b52ba32001c9c693512cc725ce6c1d331efd..ddba1308a6b3133e32d83fa7b8e5174bc5e67922 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="EntryPointsManager">
     <list size="1">
@@ -5,7 +6,7 @@
     </list>
   </component>
   <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f4aa3feabbfbc17493a756142c97c07c516d545c..8d39981a7c3b1ffd92f89342c925a310b2e41e0a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,7 @@
 import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
 
 plugins {
+    alias(libs.plugins.gradle.versions)
     alias(libs.plugins.android.application)
     alias(libs.plugins.kotlin.android)
     alias(libs.plugins.kotlinx.serialization)
@@ -70,28 +71,27 @@ android {
         }
     }
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_17
-        targetCompatibility = JavaVersion.VERSION_17
+        sourceCompatibility = JavaVersion.VERSION_19
+        targetCompatibility = JavaVersion.VERSION_19
     }
     kotlinOptions {
-        jvmTarget = "17"
+        jvmTarget = "19"
         freeCompilerArgs += "-Xcontext-receivers"
 //        freeCompilerArgs += "-opt-in=kotlin.ExperimentalStdlibApi"
 //        freeCompilerArgs += "-opt-in=kotlin.time.ExperimentalTime"
-        freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
+//        freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
         freeCompilerArgs += "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi"
         freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
 //        freeCompilerArgs += "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi"
 //        freeCompilerArgs += "-opt-in=androidx.compose.animation.ExperimentalAnimationApi"
         freeCompilerArgs += "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
-        freeCompilerArgs += "-opt-in=com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi"
     }
     buildFeatures {
         compose = true
         buildConfig = true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.5.3"
+        kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
     }
     packaging {
         resources {
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
index 3d7419aa763cb5392b99557b63a30c74e29c0d5e..996f2fa36017a60a000129d7cc4ee80a5a0db430 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
@@ -22,18 +22,18 @@ import net.novagamestudios.common_utils.compose.state.loadInitialBlocking
 import net.novagamestudios.common_utils.debug
 import net.novagamestudios.common_utils.info
 import net.novagamestudios.common_utils.toastShort
-import net.novagamestudios.kaffeekasse.api.PortalClient
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
 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.gitlab.GitLab
-import net.novagamestudios.kaffeekasse.repositories.GitLabReleases
-import net.novagamestudios.kaffeekasse.repositories.HiwiTrackerRepository
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.repositories.releases.GitLabReleases
+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.PortalRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository
 import net.novagamestudios.kaffeekasse.repositories.newSettingsStore
 
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerAPI.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerAPI.kt
index b53025551bd239b84691e730fd5fc393d56fe8d1..be4b62b1a3a49b38d72322f6dfd43c32c536eefc 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerAPI.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerAPI.kt
@@ -3,7 +3,8 @@ package net.novagamestudios.kaffeekasse.api.hiwi_tracker
 import io.ktor.http.parameters
 import kotlinx.datetime.format
 import net.novagamestudios.common_utils.Logger
-import net.novagamestudios.kaffeekasse.api.PortalClient
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
+import net.novagamestudios.kaffeekasse.api.hiwi_tracker.model.MonthDataResponse
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.MonthKey
 
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerScraper.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerScraper.kt
index e17ba275a0e2b9dfcb3ca12d6ae64e0caad13edf..8fb656fdc607e86b049f0ea5cbc2621f02528170 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerScraper.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/HiwiTrackerScraper.kt
@@ -3,7 +3,7 @@ package net.novagamestudios.kaffeekasse.api.hiwi_tracker
 import io.ktor.http.URLBuilder
 import io.ktor.http.parameters
 import kotlinx.datetime.format
-import net.novagamestudios.kaffeekasse.api.PortalClient
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry
 
 class HiwiTrackerScraper(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/MonthDataResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/model/MonthDataResponse.kt
similarity index 95%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/MonthDataResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/model/MonthDataResponse.kt
index ecb791fb4247f6b1fa441adf7da384f7c6548b25..3b735e22345f0b4044e8c1a43fe601fc6293de0a 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/MonthDataResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/hiwi_tracker/model/MonthDataResponse.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api.hiwi_tracker
+package net.novagamestudios.kaffeekasse.api.hiwi_tracker.model
 
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.SerialName
@@ -8,9 +8,9 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
-import net.novagamestudios.kaffeekasse.api.PortalAPIResponse
-import net.novagamestudios.kaffeekasse.model.ISODate
-import net.novagamestudios.kaffeekasse.model.ISOTime
+import net.novagamestudios.kaffeekasse.api.portal.model.PortalAPIResponse
+import net.novagamestudios.kaffeekasse.model.date_time.ISODate
+import net.novagamestudios.kaffeekasse.model.date_time.ISOTime
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry
 import kotlin.math.absoluteValue
 import kotlin.time.Duration
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/InfoResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/InfoResponse.kt
deleted file mode 100644
index f9a69f582e9cd4cd24659b479c83606732457c5f..0000000000000000000000000000000000000000
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/InfoResponse.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.json.JsonArray
-import kotlinx.serialization.json.JsonElement
-import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
-import net.novagamestudios.kaffeekasse.util.IntAsBooleanSerializer
-
-@Serializable
-data class UserListResponse(
-    @SerialName("user_list")
-    val userList: List<BasicUserInfo>
-)
-
-@Serializable
-data class UserInfoResponse(
-    @SerialName("user_id")
-    override val id: Int,
-    @SerialName("name")
-    override val name: String,
-    @SerialName("balance")
-    override val balance: Double,
-    @SerialName("favorites")
-    override val favorites: JsonArray = JsonArray(emptyList()),
-    @SerialName("blacklist")
-    override val blacklist: List<BlackWhiteListUserInfo>? = null,
-    @SerialName("whitelist")
-    override val whitelist: List<BlackWhiteListUserInfo>? = null
-) : ExtendedUserInfo
-
-sealed interface APIPurchaseAccount : PurchaseAccount
-
-sealed interface ExtendedUserInfo : APIPurchaseAccount {
-    override val id: Int
-    override val name: String
-    val balance: Double
-    val favorites: JsonArray
-    val blacklist: List<BlackWhiteListUserInfo>?
-    val whitelist: List<BlackWhiteListUserInfo>?
-}
-
-@Serializable
-data class BlackWhiteListUserInfo(
-    @SerialName("target_id")
-    override val id: Int,
-    @SerialName("name")
-    override val name: String
-) : APIPurchaseAccount
-
-@Serializable
-data class BasicUserInfo(
-    @SerialName("id")
-    override val id: Int,
-    @SerialName("name")
-    override val name: String,
-    @SerialName("empty_pin")
-    val noPinSet: @Serializable(with = IntAsBooleanSerializer::class) Boolean? = null
-) : APIPurchaseAccount {
-    val firstName by lazy { name.split(", ")[1] }
-    val lastName by lazy { name.split(", ")[0] }
-}
-
-@Serializable
-data class ItemListResponse(
-    @SerialName("item_list")
-    val itemList: List<Item>
-) {
-    @Serializable
-    data class Item(
-        @SerialName("item_id")
-        val id: Int,
-        @SerialName("name")
-        val originalName: String,
-        val gtin: String? = null,
-        val price: Double,
-        @SerialName("image_url")
-        val imageUrl: String? = null,
-        val sort: Int,
-        @SerialName("sort_p")
-        val sortP: Double,
-        @SerialName("itemtype_id")
-        val itemTypeId: ItemTypeId,
-        val enabled: @Serializable(with = IntAsBooleanSerializer::class) Boolean,
-        @SerialName("has_condition_reports")
-        val hasConditionReports: JsonElement
-    )
-}
-
-@Serializable
-@JvmInline
-value class ItemTypeId(val id: Int)
-
-
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPI.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPI.kt
index 0ebeeef0df74b0279fa263b0f4c0792a2cc490e5..8e8d1767ed7315d71e32616eef0c59a7b902aa4e 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPI.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPI.kt
@@ -6,8 +6,17 @@ import io.ktor.util.sha1
 import net.novagamestudios.common_utils.Logger
 import net.novagamestudios.common_utils.debug
 import net.novagamestudios.common_utils.error
-import net.novagamestudios.kaffeekasse.api.PortalClient
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseAPIResponse.Error.Code as ErrorCode
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.ItemListResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.KaffeekasseAPIResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.LoggedInUserResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.LoginDeviceResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.LoginUserResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.LogoutUserResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.PurchaseResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.UserInfoResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.UserListResponse
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.KaffeekasseAPIResponse.Error.Code as ErrorCode
 
 class KaffeekasseAPI(
     client: PortalClient
@@ -72,9 +81,9 @@ class KaffeekasseAPI(
         listOf("user_id" to "$userId")
     )
 
-    suspend fun itemList(itemTypeId: ItemTypeId? = null) = invokeFunction<ItemListResponse>(
+    suspend fun itemList(itemTypeId: Int? = null) = invokeFunction<ItemListResponse>(
         name = "item_list",
-        listOfNotNull(itemTypeId?.let { "item_type_id" to "${it.id}" })
+        listOfNotNull(itemTypeId?.let { "item_type_id" to "$it" })
     )
 
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
index f277aded50c200900bdeb3f10ba0eec783f18f86..aa790757b56d0d8347f908aae51f7658565e5c4c 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
@@ -13,10 +13,9 @@ import it.skrape.selects.html5.table
 import it.skrape.selects.html5.td
 import it.skrape.selects.html5.tr
 import net.novagamestudios.common_utils.info
-import net.novagamestudios.kaffeekasse.api.PortalClient
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem
-import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItemGroup
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/APIPurchaseAccount.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/APIPurchaseAccount.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d1a4905a827a08678b7de54575997f1235d8d3ec
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/APIPurchaseAccount.kt
@@ -0,0 +1,5 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
+
+sealed interface APIPurchaseAccount : PurchaseAccount
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ce8efcd77dacc21c1a7ed1f9bd4dba36f0a7ae79
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt
@@ -0,0 +1,18 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.util.IntAsBooleanSerializer
+
+@Serializable
+data class BasicUserInfo(
+    @SerialName("id")
+    override val id: Int,
+    @SerialName("name")
+    override val name: String,
+    @SerialName("empty_pin")
+    val noPinSet: @Serializable(with = IntAsBooleanSerializer::class) Boolean? = null
+) : APIPurchaseAccount {
+    val firstName by lazy { name.split(", ")[1] }
+    val lastName by lazy { name.split(", ")[0] }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cb98eaee42a6b1b559a12d149ca18f25d7d1cf07
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt
@@ -0,0 +1,12 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BlackWhiteListUserInfo(
+    @SerialName("target_id")
+    override val id: Int,
+    @SerialName("name")
+    override val name: String
+) : APIPurchaseAccount
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/ItemListResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/ItemListResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6960eb3b2a2b9624719b51d7ddf9c36e683a3891
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/ItemListResponse.kt
@@ -0,0 +1,32 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonElement
+import net.novagamestudios.kaffeekasse.util.IntAsBooleanSerializer
+
+@Serializable
+data class ItemListResponse(
+    @SerialName("item_list")
+    val itemList: List<Item>
+) {
+    @Serializable
+    data class Item(
+        @SerialName("item_id")
+        val id: Int,
+        @SerialName("name")
+        val originalName: String,
+        val gtin: String? = null,
+        val price: Double,
+        @SerialName("image_url")
+        val imageUrl: String? = null,
+        val sort: Int,
+        @SerialName("sort_p")
+        val sortP: Double,
+        @SerialName("itemtype_id")
+        val itemTypeId: Int,
+        val enabled: @Serializable(with = IntAsBooleanSerializer::class) Boolean,
+        @SerialName("has_condition_reports")
+        val hasConditionReports: JsonElement
+    )
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPIResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/KaffeekasseAPIResponse.kt
similarity index 89%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPIResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/KaffeekasseAPIResponse.kt
index 7e291701335160ac6e3f02545c9c1174dd42803f..ec300f9d0f65a4f085c931623ccbf38bdca89eda 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseAPIResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/KaffeekasseAPIResponse.kt
@@ -1,7 +1,7 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.Serializable
-import net.novagamestudios.kaffeekasse.api.PortalAPIResponse
+import net.novagamestudios.kaffeekasse.api.portal.model.PortalAPIResponse
 
 
 @Serializable
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoggedInUserResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoggedInUserResponse.kt
similarity index 86%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoggedInUserResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoggedInUserResponse.kt
index b2e9b9f1d0a2ab8a5bfd066fa21981adf5fc062b..fd24ec875f6c7c375eab3bf442d0a8a8a62932a0 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoggedInUserResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoggedInUserResponse.kt
@@ -1,8 +1,8 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.novagamestudios.kaffeekasse.api.PortalAPIResponse
+import net.novagamestudios.kaffeekasse.api.portal.model.PortalAPIResponse
 
 
 @Serializable
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginDeviceResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginDeviceResponse.kt
similarity index 81%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginDeviceResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginDeviceResponse.kt
index 067bf6ad51dfdb2a74114f7a7b04c1b43f8f2f90..b1fc40956494a25a05226e4ce35adb78ac45de0c 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginDeviceResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginDeviceResponse.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginUserResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginUserResponse.kt
similarity index 74%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginUserResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginUserResponse.kt
index 554cb5c31c9988bc1394e88abe28cd2f4f0a2906..19e763274d09e122016c716a58d86d360595f38b 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LoginUserResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LoginUserResponse.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LogoutUserResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LogoutUserResponse.kt
similarity index 56%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LogoutUserResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LogoutUserResponse.kt
index 5c9929b258c666caeef6675d95c89e3cdd922e65..6dfc8e632bf98c69bd06ccf7ec6f5a5d7d605420 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/LogoutUserResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/LogoutUserResponse.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.Serializable
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/PurchaseResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/PurchaseResponse.kt
similarity index 57%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/PurchaseResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/PurchaseResponse.kt
index 30d56bbd9d65718d21a57f421ee632b2a3d6a4ef..803abc8d9349456be1e70aad5b4dc4ca729d01aa 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/PurchaseResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/PurchaseResponse.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api.kaffeekasse
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.Serializable
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3ef3fbbe0e5ae2a9d83117045a47ebee1d6078ad
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt
@@ -0,0 +1,31 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonArray
+
+@Serializable
+data class UserInfoResponse(
+    @SerialName("user_id")
+    override val id: Int,
+    @SerialName("name")
+    override val name: String,
+    @SerialName("balance")
+    override val balance: Double,
+    @SerialName("favorites")
+    override val favorites: JsonArray = JsonArray(emptyList()),
+    @SerialName("blacklist")
+    override val blacklist: List<BlackWhiteListUserInfo>? = null,
+    @SerialName("whitelist")
+    override val whitelist: List<BlackWhiteListUserInfo>? = null
+) : ExtendedUserInfo
+
+sealed interface ExtendedUserInfo : APIPurchaseAccount {
+    override val id: Int
+    override val name: String
+    val balance: Double
+    val favorites: JsonArray
+    val blacklist: List<BlackWhiteListUserInfo>?
+    val whitelist: List<BlackWhiteListUserInfo>?
+}
+
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserListResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserListResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..61a49462c5db46ea7f8b59135f951adb8109a642
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserListResponse.kt
@@ -0,0 +1,10 @@
+package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UserListResponse(
+    @SerialName("user_list")
+    val userList: List<BasicUserInfo>
+)
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalClient.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt
similarity index 94%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalClient.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt
index 7eb335ec5ac1d33e52b949b20da4031e3a4959b9..4922e8c8c86e285552ce73f1c32e6ac867988b7c 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalClient.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/PortalClient.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.api
+package net.novagamestudios.kaffeekasse.api.portal
 
 import androidx.compose.runtime.mutableStateListOf
 import io.ktor.client.HttpClient
@@ -22,6 +22,7 @@ import it.skrape.core.htmlDocument
 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
@@ -36,8 +37,9 @@ import net.novagamestudios.common_utils.error
 import net.novagamestudios.common_utils.info
 import net.novagamestudios.common_utils.verbose
 import net.novagamestudios.common_utils.warn
-import net.novagamestudios.kaffeekasse.model.Login
-import net.novagamestudios.kaffeekasse.model.isValid
+import net.novagamestudios.kaffeekasse.api.portal.model.PortalAPIResponse
+import net.novagamestudios.kaffeekasse.model.credentials.Login
+import net.novagamestudios.kaffeekasse.model.credentials.isValid
 import net.novagamestudios.kaffeekasse.util.MutableCookiesStorage
 
 class PortalClient(
@@ -45,6 +47,7 @@ class PortalClient(
 ) : Logger {
     private val baseUrl = Url("https://embedded.rwth-aachen.de")
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     private val computationScope = coroutineScope.newCoroutineContext(Dispatchers.IO)
 
     private val jsonFormat by lazy {
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalAPIResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/model/PortalAPIResponse.kt
similarity index 89%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalAPIResponse.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/model/PortalAPIResponse.kt
index 8320ed75f1fd5304ebda957bb1a286db2beb7234..9bf63519a877f27e5109a47cef26f66c14164d28 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/PortalAPIResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/portal/model/PortalAPIResponse.kt
@@ -1,10 +1,10 @@
-package net.novagamestudios.kaffeekasse.api
+package net.novagamestudios.kaffeekasse.api.portal.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.JsonElement
 import kotlinx.serialization.json.JsonNull
-import net.novagamestudios.kaffeekasse.model.User
+import net.novagamestudios.kaffeekasse.model.credentials.User
 
 @Serializable
 open class PortalAPIResponse {
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackage.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackage.kt
index 0c4163c11cc0b0ebadd1293220f9f360d5ca42ce..45c037a8b8ad6e10baea06482784391e29883139 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackage.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackage.kt
@@ -2,8 +2,8 @@ package net.novagamestudios.kaffeekasse.gitlab
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.novagamestudios.kaffeekasse.model.AppVersion
-import net.novagamestudios.kaffeekasse.model.ISODateTime
+import net.novagamestudios.kaffeekasse.model.app.AppVersion
+import net.novagamestudios.kaffeekasse.model.date_time.ISODateTime
 
 @Serializable
 data class GitLabPackage(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackageFile.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackageFile.kt
index ae0f0106a6d663219c04c404e060ba78d3cec54c..0a61324dd15b4e7a76e81479beae3ed26fa9099d 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackageFile.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabPackageFile.kt
@@ -2,7 +2,7 @@ package net.novagamestudios.kaffeekasse.gitlab
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.novagamestudios.kaffeekasse.model.ISODateTime
+import net.novagamestudios.kaffeekasse.model.date_time.ISODateTime
 
 @Serializable
 data class GitLabPackageFile(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabRelease.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabRelease.kt
index e411c6688472b839a49c9b9e8f96106a1a8f83f0..40aea1f5f07a7ae236f747e84ae11928656b4826 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabRelease.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/gitlab/GitLabRelease.kt
@@ -2,8 +2,8 @@ package net.novagamestudios.kaffeekasse.gitlab
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.novagamestudios.kaffeekasse.model.AppVersion
-import net.novagamestudios.kaffeekasse.model.ISODateTime
+import net.novagamestudios.kaffeekasse.model.app.AppVersion
+import net.novagamestudios.kaffeekasse.model.date_time.ISODateTime
 
 @Serializable
 data class GitLabRelease(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/ISODateTime.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/ISODateTime.kt
deleted file mode 100644
index 9453fb24a1a5d5253218ad969a6ece135d0691f6..0000000000000000000000000000000000000000
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/ISODateTime.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package net.novagamestudios.kaffeekasse.model
-
-import kotlinx.datetime.LocalDate
-import kotlinx.datetime.LocalDateTime
-import kotlinx.datetime.LocalTime
-import kotlinx.datetime.toJavaLocalDate
-import kotlinx.datetime.toJavaLocalDateTime
-import kotlinx.datetime.toJavaLocalTime
-import kotlinx.datetime.toKotlinLocalDate
-import kotlinx.datetime.toKotlinLocalDateTime
-import kotlinx.datetime.toKotlinLocalTime
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.descriptors.PrimitiveKind
-import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
-import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import java.time.format.DateTimeFormatter
-import java.time.LocalDate as JavaLocalDate
-import java.time.LocalDateTime as JavaLocalDateTime
-import java.time.LocalTime as JavaLocalTime
-
-typealias ISODateTime = @Serializable(with = ISODateTimeSerializer::class) LocalDateTime
-class ISODateTimeSerializer : KSerializer<LocalDateTime> {
-    private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME!!
-    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ISODateTime", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: LocalDateTime) {
-        encoder.encodeString(value.format(formatter))
-    }
-    override fun deserialize(decoder: Decoder): LocalDateTime {
-        return LocalDateTime.parse(decoder.decodeString(), formatter)
-    }
-}
-
-typealias ISODate = @Serializable(with = ISODateSerializer::class) LocalDate
-class ISODateSerializer : KSerializer<LocalDate> {
-    private val formatter = DateTimeFormatter.ISO_DATE!!
-    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ISODate", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: LocalDate) {
-        encoder.encodeString(value.format(formatter))
-    }
-    override fun deserialize(decoder: Decoder): LocalDate {
-        return LocalDate.parse(decoder.decodeString(), formatter)
-    }
-}
-
-typealias ISOTime = @Serializable(with = ISOTimeSerializer::class) LocalTime
-class ISOTimeSerializer : KSerializer<LocalTime> {
-    private val formatter = DateTimeFormatter.ISO_TIME!!
-    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ISOTime", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: LocalTime) {
-        encoder.encodeString(value.format(formatter))
-    }
-    override fun deserialize(decoder: Decoder): LocalTime {
-        val string = decoder.decodeString()
-        if (string.startsWith("24:")) return JavaLocalTime.MAX.toKotlinLocalTime()
-        return LocalTime.parse(string, formatter)
-    }
-}
-
-
-fun LocalDateTime.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalDateTime())!!
-fun LocalDateTime.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalDateTime.parse(input, formatter).toKotlinLocalDateTime()
-
-fun LocalDate.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalDate())!!
-fun LocalDate.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalDate.parse(input, formatter).toKotlinLocalDate()
-
-fun LocalTime.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalTime())!!
-fun LocalTime.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalTime.parse(input, formatter).toKotlinLocalTime()
-
-
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/User.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/User.kt
deleted file mode 100644
index 380684f0a79bd47692e0bc457d6b8edeef3208cb..0000000000000000000000000000000000000000
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/User.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package net.novagamestudios.kaffeekasse.model
-
-import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItemGroup
-
-data class User(
-    val user: String? = null,
-    val displayName: String? = null
-)
-
-
-val User?.isKaffeekasse get() = this != null && user == "kaffeekasse"
-val User?.isUser get() = this != null && !isKaffeekasse
-
-data class Device(
-    val name: String?,
-    val itemTypeId: Int?
-) {
-    val knownItemGroup by lazy { itemTypeId?.let { KnownItemGroup.byId[it] } }
-}
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/AppRelease.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppRelease.kt
similarity index 79%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/model/AppRelease.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppRelease.kt
index 3cd0de2be8b683006afaf4e2d9159e6f33c2e68b..838384ee9bb33c99dec7b1eadecd79f0b5528cc3 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/AppRelease.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppRelease.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.model
+package net.novagamestudios.kaffeekasse.model.app
 
 import kotlinx.datetime.LocalDateTime
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/AppVersion.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppVersion.kt
similarity index 95%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/model/AppVersion.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppVersion.kt
index f361c6d7f95052c07f8fd1d99554c7b12cb85966..dba08060809986bc560b5e0dac0593281ed49332 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/AppVersion.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/app/AppVersion.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.model
+package net.novagamestudios.kaffeekasse.model.app
 
 import kotlinx.serialization.Serializable
 import net.novagamestudios.kaffeekasse.BuildConfig
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Device.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Device.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0954f5a832aab8963cfbaa262d1c99f70cdcae8d
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Device.kt
@@ -0,0 +1,10 @@
+package net.novagamestudios.kaffeekasse.model.credentials
+
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItemGroup
+
+data class Device(
+    val name: String?,
+    val itemTypeId: Int?
+) {
+    val knownItemGroup by lazy { itemTypeId?.let { KnownItemGroup.byId[it] } }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/DeviceCredentials.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/DeviceCredentials.kt
similarity index 62%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/model/DeviceCredentials.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/DeviceCredentials.kt
index cb49dbaf5b9627eda8d3f86b6f5506354911adaa..9cda0b69708fb799c84733739ae926550553a579 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/DeviceCredentials.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/DeviceCredentials.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.model
+package net.novagamestudios.kaffeekasse.model.credentials
 
 import kotlinx.serialization.Serializable
 
@@ -12,4 +12,3 @@ data class DeviceCredentials(
     }
 }
 
-val DeviceCredentials.isValid get() = deviceId.isNotBlank() && apiKey.isNotBlank()
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..801695db8b2be55f071a2c870bba1711011ade38
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt
@@ -0,0 +1,8 @@
+package net.novagamestudios.kaffeekasse.model.credentials
+
+val User?.isKaffeekasse get() = this != null && user == "kaffeekasse"
+val User?.isUser get() = this != null && !isKaffeekasse
+
+val Login.isValid get() = username.isNotBlank() && password.isNotBlank()
+
+val DeviceCredentials.isValid get() = deviceId.isNotBlank() && apiKey.isNotBlank()
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/Login.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Login.kt
similarity index 62%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/model/Login.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Login.kt
index 893b4fcd0609f5710e6dc553f6fa437b88426289..d80b1e088f01d968ab7b2993e0902698b0047b8f 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/Login.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Login.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.model
+package net.novagamestudios.kaffeekasse.model.credentials
 
 import kotlinx.serialization.Serializable
 
@@ -12,4 +12,3 @@ data class Login(
     }
 }
 
-val Login.isValid get() = username.isNotBlank() && password.isNotBlank()
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/User.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/User.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9c5b6006bad107994b7d9992fbe0c5dfb6a6010c
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/User.kt
@@ -0,0 +1,7 @@
+package net.novagamestudios.kaffeekasse.model.credentials
+
+data class User(
+    val user: String? = null,
+    val displayName: String? = null
+)
+
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/Extensions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/Extensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ff09f5eaa799b8a7404d93b914c55684030088de
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/Extensions.kt
@@ -0,0 +1,32 @@
+package net.novagamestudios.kaffeekasse.model.date_time
+
+import kotlinx.datetime.LocalDate
+import kotlinx.datetime.LocalDateTime
+import kotlinx.datetime.LocalTime
+import kotlinx.datetime.toJavaLocalDate
+import kotlinx.datetime.toJavaLocalDateTime
+import kotlinx.datetime.toJavaLocalTime
+import kotlinx.datetime.toKotlinLocalDate
+import kotlinx.datetime.toKotlinLocalDateTime
+import kotlinx.datetime.toKotlinLocalTime
+import java.time.format.DateTimeFormatter
+import java.time.LocalDate as JavaLocalDate
+import java.time.LocalDateTime as JavaLocalDateTime
+import java.time.LocalTime as JavaLocalTime
+
+fun LocalDateTime.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalDateTime())!!
+fun LocalDateTime.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalDateTime.parse(
+    input,
+    formatter
+).toKotlinLocalDateTime()
+fun LocalDate.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalDate())!!
+fun LocalDate.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalDate.parse(
+    input,
+    formatter
+).toKotlinLocalDate()
+
+fun LocalTime.format(formatter: DateTimeFormatter) = formatter.format(toJavaLocalTime())!!
+fun LocalTime.Companion.parse(input: CharSequence, formatter: DateTimeFormatter) = JavaLocalTime.parse(
+    input,
+    formatter
+).toKotlinLocalTime()
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODate.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODate.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d1ac06f590f4731869586b7d2184cc8b8cfda63e
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODate.kt
@@ -0,0 +1,7 @@
+package net.novagamestudios.kaffeekasse.model.date_time
+
+import kotlinx.datetime.LocalDate
+import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.model.date_time.serializers.ISODateSerializer
+
+typealias ISODate = @Serializable(with = ISODateSerializer::class) LocalDate
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODateTime.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODateTime.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b1b601e751d74617deb9f2ccefe7e004644370ba
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISODateTime.kt
@@ -0,0 +1,7 @@
+package net.novagamestudios.kaffeekasse.model.date_time
+
+import kotlinx.datetime.LocalDateTime
+import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.model.date_time.serializers.ISODateTimeSerializer
+
+typealias ISODateTime = @Serializable(with = ISODateTimeSerializer::class) LocalDateTime
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISOTime.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISOTime.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8973362d4cdbb77726f9031df708e364b49292cf
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/ISOTime.kt
@@ -0,0 +1,7 @@
+package net.novagamestudios.kaffeekasse.model.date_time
+
+import kotlinx.datetime.LocalTime
+import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.model.date_time.serializers.ISOTimeSerializer
+
+typealias ISOTime = @Serializable(with = ISOTimeSerializer::class) LocalTime
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateSerializer.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..121066f973f8bfe307ad978e27af63284f53179b
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateSerializer.kt
@@ -0,0 +1,24 @@
+package net.novagamestudios.kaffeekasse.model.date_time.serializers
+
+import kotlinx.datetime.LocalDate
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import net.novagamestudios.kaffeekasse.model.date_time.format
+import net.novagamestudios.kaffeekasse.model.date_time.parse
+import java.time.format.DateTimeFormatter
+
+class ISODateSerializer : KSerializer<LocalDate> {
+    private val formatter = DateTimeFormatter.ISO_DATE!!
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("ISODate", PrimitiveKind.STRING)
+    override fun serialize(encoder: Encoder, value: LocalDate) {
+        encoder.encodeString(value.format(formatter))
+    }
+    override fun deserialize(decoder: Decoder): LocalDate {
+        return LocalDate.parse(decoder.decodeString(), formatter)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateTimeSerializer.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateTimeSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e73613acbf18137149f11552d71afa8ce8c818b
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISODateTimeSerializer.kt
@@ -0,0 +1,24 @@
+package net.novagamestudios.kaffeekasse.model.date_time.serializers
+
+import kotlinx.datetime.LocalDateTime
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import net.novagamestudios.kaffeekasse.model.date_time.format
+import net.novagamestudios.kaffeekasse.model.date_time.parse
+import java.time.format.DateTimeFormatter
+
+class ISODateTimeSerializer : KSerializer<LocalDateTime> {
+    private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME!!
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("ISODateTime", PrimitiveKind.STRING)
+    override fun serialize(encoder: Encoder, value: LocalDateTime) {
+        encoder.encodeString(value.format(formatter))
+    }
+    override fun deserialize(decoder: Decoder): LocalDateTime {
+        return LocalDateTime.parse(decoder.decodeString(), formatter)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISOTimeSerializer.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISOTimeSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f48b7e374d1caffe403164a1cf1632f823d8d870
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/date_time/serializers/ISOTimeSerializer.kt
@@ -0,0 +1,28 @@
+package net.novagamestudios.kaffeekasse.model.date_time.serializers
+
+import kotlinx.datetime.LocalTime
+import kotlinx.datetime.toKotlinLocalTime
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import net.novagamestudios.kaffeekasse.model.date_time.format
+import net.novagamestudios.kaffeekasse.model.date_time.parse
+import java.time.format.DateTimeFormatter
+import java.time.LocalTime as JavaLocalTime
+
+class ISOTimeSerializer : KSerializer<LocalTime> {
+    private val formatter = DateTimeFormatter.ISO_TIME!!
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("ISOTime", PrimitiveKind.STRING)
+    override fun serialize(encoder: Encoder, value: LocalTime) {
+        encoder.encodeString(value.format(formatter))
+    }
+    override fun deserialize(decoder: Decoder): LocalTime {
+        val string = decoder.decodeString()
+        if (string.startsWith("24:")) return JavaLocalTime.MAX.toKotlinLocalTime()
+        return LocalTime.parse(string, formatter)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Account.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Account.kt
index e5643f4b084510cbf03fe974b75c81683abad424..4c350c095e0d61e941c3525c73a4717027fea3a1 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Account.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Account.kt
@@ -9,9 +9,4 @@ data class Account(
     val deposited: Double
 )
 
-interface PurchaseAccount {
-    val id: Int
-    val name: String
-}
-
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Items.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Item.kt
similarity index 58%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Items.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Item.kt
index 0a35f2c19e260c9ba2224f4de2af2153482999f7..1fe9f976eb048b7234c8038ba34448e1fcf03702 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Items.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Item.kt
@@ -18,29 +18,3 @@ interface Item {
     val imageDrawable: Int?
     val imageUrl: String?
 }
-
-
-enum class ItemCategory {
-    ColdBeverage,
-    HotBeverage,
-    Snack,
-    IceCream,
-    Food,
-    Fruit,
-    Other
-}
-
-
-interface ItemGroup {
-    val id: Int?
-    val originalName: String?
-
-    val name: String
-    val items: List<Item>
-}
-
-
-interface Stock {
-    val itemGroups: List<ItemGroup>
-}
-
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemCategory.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemCategory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..87c888c40759c86987b03fb4b20144d701651711
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemCategory.kt
@@ -0,0 +1,11 @@
+package net.novagamestudios.kaffeekasse.model.kaffeekasse
+
+enum class ItemCategory {
+    ColdBeverage,
+    HotBeverage,
+    Snack,
+    IceCream,
+    Food,
+    Fruit,
+    Other
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemGroup.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemGroup.kt
new file mode 100644
index 0000000000000000000000000000000000000000..06b72db8920a7e63586e54b1166ca9f6796b2a6d
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ItemGroup.kt
@@ -0,0 +1,9 @@
+package net.novagamestudios.kaffeekasse.model.kaffeekasse
+
+interface ItemGroup {
+    val id: Int?
+    val originalName: String?
+
+    val name: String
+    val items: List<Item>
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItem.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItem.kt
index 5209680dacd49da7dbeb8c293f6bd2618d9a1ea8..35f502543207f0ecd9b4c48b601e8be21e5ef990 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItem.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItem.kt
@@ -3,86 +3,6 @@ package net.novagamestudios.kaffeekasse.model.kaffeekasse
 import kotlinx.serialization.Serializable
 
 
-/*
-KaffeekasseManualTransactionDetails(
-    accounts=[
-        Account(name=Broeckmann, Jonas, id=339, isDefault=true)
-    ],
-    itemGroups=[
-        ItemGroup(
-            name=Küche,
-            items=[
-                Item(name=Almdudler, id=221),
-                Item(name=Bier (0,33L auch alk.frei–außer Leffe/Erdinger), id=110),
-                Item(name=Bier (0,5 L) auch alk.frei - kein Erdinger-, id=80),
-                Item(name=Café au lait, id=163),
-                Item(name=Cappuccino, id=159),
-                Item(name=Club-Mate, id=108),
-                Item(name=Coca Cola (hi-carb), id=77),
-                Item(name=Coke ZERO, id=78),
-                Item(name=Coke Zero koffeinfrei, id=246),
-                Item(name=Duplex Farbe, id=155),
-                Item(name=Chouffe Bier, id=262),
-                Item(name=Duplex S/W, id=91),
-                Item(name=Eifler Landbier, id=192),
-                Item(name=Eis: Cuja Mara Split, id=170),
-                Item(name=Eis: Magnum / NUII, id=124),
-                Item(name=Engelbert Apfel, id=127),
-                Item(name=Engelbert Naturell, id=240),
-                Item(name=Engelbert/Rheinfels Sprudel, id=79),
-                Item(name=Erdinger Alkoholfrei (0,5l), id=242),
-                Item(name=Erdnüsse, id=87),
-                Item(name=Espresso, id=160),
-                Item(name=Fanta, id=81),
-                Item(name=Fassbrause/Bionade, id=199),
-                Item(name=Gerolsteiner, id=83),
-                Item(name=Hanuta, id=209),
-                Item(name=Haribo-Beutel, id=149),
-                Item(name=Kaffee klein, id=84),
-                Item(name=Kaffee groß, id=165),
-                Item(name=Erdinger Alkoholfrei (0.33l), id=261),
-                Item(name=Knoppers Riegel, id=214),
-                Item(name=Latte Macchiato, id=161),
-                Item(name=Leffe, id=191),
-                Item(name=Milka, id=152),
-                Item(name=Moccachoc, id=162),
-                Item(name=Pizza, id=243),
-                Item(name=Rockstar/Monster (inkl. Pfand), id=187),
-                Item(name=Schoko-Creme (Kakao), id=164),
-                Item(name=Simplex Farbe, id=140),
-                Item(name=Simplex S/W, id=90),
-                Item(name=Taschentücher, id=158),
-                Item(name=Apfel, id=195),
-                Item(name=Balisto, id=105),
-                Item(name=Duplo, id=231),
-                Item(name=Eis: Cornetto, id=128),
-                Item(name=Eis: Nogger, id=101),
-                Item(name=Oreo Kekse, id=235),
-                Item(name=CocaCola 0,5l GLAS, id=265),
-                Item(name=Trolli, id=249),
-                Item(name=Twix, id=93),
-                Item(name=Yogurette, id=217),
-                Item(name=Kinder Schokolade, id=220),
-                Item(name=Nuts Royal Nüsse, id=227),
-                Item(name=Mandarine/Clementine, id=213),
-                Item(name=Milch (Maschine), id=263)
-            ]
-        ),
-        ItemGroup(
-            name=Getränkeraum,
-            items=[
-                Item(name=MioMio Mate, id=259)
-            ]
-        ), ItemGroup(
-            name=Farbdrucker,
-            items=[
-                Item(name=3D-Druck pro gramm, id=256),
-                Item(name=Euglüh, id=173)
-            ]
-        )
-    ]
-)
-*/
 @Serializable
 enum class KnownItem(
     val id: Int,
@@ -164,18 +84,3 @@ enum class KnownItem(
     }
 }
 
-
-enum class KnownItemGroup(
-    val id: Int,
-    val originalName: String
-) {
-    Kueche       (5, "Küche"),
-    Getraenkeraum(6, "Getränkeraum"),
-    Farbdrucker  (7, "Farbdrucker"),
-    ;
-    companion object {
-        val byId by lazy { entries.associateBy { it.id } }
-        val byOriginalName by lazy { entries.associateBy { it.originalName } }
-    }
-}
-
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItemGroup.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItemGroup.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a82fa956552905808f638a490716b5274e63434e
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/KnownItemGroup.kt
@@ -0,0 +1,15 @@
+package net.novagamestudios.kaffeekasse.model.kaffeekasse
+
+enum class KnownItemGroup(
+    val id: Int,
+    val originalName: String
+) {
+    Kueche       (5, "Küche"),
+    Getraenkeraum(6, "Getränkeraum"),
+    Farbdrucker  (7, "Farbdrucker"),
+    ;
+    companion object {
+        val byId by lazy { entries.associateBy { it.id } }
+        val byOriginalName by lazy { entries.associateBy { it.originalName } }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ffc199cc07122ffa66d126f21a5f4c9f1cd9c590
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt
@@ -0,0 +1,6 @@
+package net.novagamestudios.kaffeekasse.model.kaffeekasse
+
+interface PurchaseAccount {
+    val id: Int
+    val name: String
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Stock.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Stock.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c27179bcf395bbc2071b671f19084bf93c3bfc9b
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/Stock.kt
@@ -0,0 +1,5 @@
+package net.novagamestudios.kaffeekasse.model.kaffeekasse
+
+interface Stock {
+    val itemGroups: List<ItemGroup>
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Credentials.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Credentials.kt
index 6168113e4c899a595f37a660cbb91e18f317b58d..4a35b181a8081411b5835aa085eeb66db3246a10 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Credentials.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Credentials.kt
@@ -17,8 +17,8 @@ import net.novagamestudios.common_utils.error
 import net.novagamestudios.common_utils.info
 import net.novagamestudios.common_utils.toastShort
 import net.novagamestudios.kaffeekasse.BuildConfig
-import net.novagamestudios.kaffeekasse.model.DeviceCredentials
-import net.novagamestudios.kaffeekasse.model.Login
+import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
+import net.novagamestudios.kaffeekasse.model.credentials.Login
 
 class Credentials(
     private val credentialManager: CredentialManager,
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/I11Client.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/I11Client.kt
deleted file mode 100644
index f11fa5802ada84ef1d011a56977cee3d772779da..0000000000000000000000000000000000000000
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/I11Client.kt
+++ /dev/null
@@ -1,464 +0,0 @@
-package net.novagamestudios.kaffeekasse.repositories
-
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import io.ktor.client.HttpClient
-import io.ktor.client.call.body
-import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
-import io.ktor.client.plugins.cookies.HttpCookies
-import io.ktor.client.plugins.cookies.cookies
-import io.ktor.client.request.HttpRequestBuilder
-import io.ktor.client.request.forms.submitForm
-import io.ktor.client.statement.HttpResponse
-import io.ktor.http.ContentType
-import io.ktor.http.Cookie
-import io.ktor.http.Parameters
-import io.ktor.http.URLBuilder
-import io.ktor.http.Url
-import io.ktor.http.parameters
-import io.ktor.http.renderCookieHeader
-import io.ktor.serialization.kotlinx.json.json
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.newCoroutineContext
-import kotlinx.serialization.json.Json
-import net.novagamestudios.common_utils.Logger
-import net.novagamestudios.common_utils.debug
-import net.novagamestudios.common_utils.info
-import net.novagamestudios.kaffeekasse.api.PortalAPIResponse
-import net.novagamestudios.kaffeekasse.model.Login
-import net.novagamestudios.kaffeekasse.model.isValid
-import net.novagamestudios.kaffeekasse.util.MutableCookiesStorage
-import net.novagamestudios.kaffeekasse.util.isExpired
-
-
-@Deprecated("Use PortalRepository instead")
-class I11Client(
-    coroutineScope: CoroutineScope
-) : Logger {
-
-    private val computationScope = coroutineScope.newCoroutineContext(Dispatchers.IO)
-
-    private val cookiesStorage by lazy { MutableCookiesStorage(mutableStateListOf()) }
-    private val client = HttpClient {
-        install(HttpCookies) {
-            storage = cookiesStorage
-        }
-        install(ContentNegotiation) {
-            json(
-                Json {
-                    prettyPrint = true
-                    isLenient = true
-                    ignoreUnknownKeys = true
-                },
-                contentType = ContentType.Text.Any // Uses "text/json"
-            )
-        }
-    }
-
-
-    val portal = Portal()
-//    val kaffeekasse = Kaffeekasse()
-//    val hiwiTracker = HiwiTracker()
-
-    private val modules: List<Module<*>> = listOf(portal)
-
-
-    private var lastTriedLogin: Login? = null
-    private var session by mutableStateOf<Session?>(null)
-    var loginErrors by mutableStateOf<List<String>?>(null)
-        private set
-    val isLoggedIn get() = session != null
-
-    private suspend fun performLogin(login: Login): Session {
-        debug { "performing login" }
-        if (!login.isValid) throw IllegalArgumentException("Invalid login")
-        lastTriedLogin = login
-        session = null
-        loginErrors = null
-        modules.forEach { it.invalidateData() }
-        info { "logging in as ${login.username}" }
-        portal.fetchData(parameters {
-            append("username", login.username)
-            append("password", login.password)
-            append("login", "")
-        })
-        val response = portal.jsonData.value
-        val sessionCookie = client.cookies(portal.baseUrl)
-            .firstOrNull { it.name == SessionCookieName && !it.isExpired }
-        if (response == null || !response.session.isLoggedIn || sessionCookie == null) {
-            debug { "login failed" }
-            modules.forEach { it.invalidateData() }
-            loginErrors = response?.errors ?: emptyList()
-            throw IllegalStateException("Login failed")
-        }
-        debug { "login successful" }
-        return Session(sessionCookie).also { session = it }
-    }
-
-    suspend fun login(login: Login) {
-        debug { "login" }
-        if (!login.isValid) throw IllegalArgumentException("Invalid login")
-        if (login == lastTriedLogin && isLoggedIn) return
-        performLogin(login)
-    }
-    suspend fun logout() {
-        debug { "logout" }
-        lastTriedLogin = null
-        session = null
-        loginErrors = null
-        modules.forEach { it.invalidateData() }
-    }
-    private suspend fun requireSession(): Session = session?.takeIf { it.isValid } ?: performLogin(
-        lastTriedLogin ?: throw IllegalStateException("Not logged in and no login credentials provided")
-    )
-    private data class Session(
-        private val sessionCookie: Cookie
-    ) {
-        val isValid get() = !sessionCookie.isExpired
-        context(HttpRequestBuilder)
-        fun apply() {
-            headers["Cookie"] = renderCookieHeader(sessionCookie)
-        }
-    }
-
-
-    companion object {
-        private const val SessionCookieName = "PORTALSESSID"
-    }
-
-
-    abstract inner class Module<T>(
-        val baseUrl: Url
-    ) {
-        val isLoggedIn get() = this@I11Client.isLoggedIn
-
-        protected val jsonUrl = URLBuilder(baseUrl).apply {
-            parameters["json"] = ""
-        }.build()
-
-        val jsonData = MutableStateFlow<T?>(null)
-        var jsonDataDirty by mutableStateOf(false)
-            protected set
-
-        suspend fun fetchData(formParameters: Parameters = Parameters.Empty) {
-            val session = if ("login" in formParameters) null else requireSession()
-            val response = client.submitForm(
-                url = "$jsonUrl",
-                formParameters = formParameters
-            ) {
-                session?.apply()
-            }
-            jsonData.value = response.toData()
-            jsonDataDirty = false
-        }
-        protected abstract suspend fun HttpResponse.toData(): T
-
-        open suspend fun invalidateData() {
-            cookiesStorage.clear(baseUrl)
-            jsonData.value = null
-        }
-    }
-
-    inner class Portal : Module<PortalAPIResponse>(Url("https://portal.embedded.rwth-aachen.de")) {
-        override suspend fun HttpResponse.toData(): PortalAPIResponse {
-            return body<PortalAPIResponse>()
-        }
-    }
-//    @Serializable
-//    private data class I11PortalDataImpl(
-//        override val session: PortalAPIResponse.Session,
-//        override val infos: List<String>,
-//        override val warnings: List<String>,
-//        override val errors: List<String>,
-//        override val navigation: JsonElement,
-//        @SerialName("ajaxui")
-//        override val ajaxUI: PortalAPIResponse.AjaxUI? = null
-//    ) : PortalAPIResponse
-
-    /*inner class Kaffeekasse : Module<KaffeekasseData>(Url("https://kaffeekasse.embedded.rwth-aachen.de")) {
-        val manuaBillUrl = URLBuilder(baseUrl).apply {
-            parameters["manualbill"] = ""
-        }.build()
-        val transactionsUrl = URLBuilder(baseUrl).apply {
-            parameters["balance"] = ""
-        }.build()
-
-        val manualBillDetails = MutableStateFlow<ManualBillDetails?>(null)
-        val transactions = MutableStateFlow<List<Transaction>?>(null)
-
-        var manualBillDetailsDirty by mutableStateOf(false)
-            private set
-        var transactionsDirty by mutableStateOf(false)
-            private set
-
-        override suspend fun HttpResponse.toData(): KaffeekasseData {
-            return body<KaffeekasseData>()
-        }
-
-        override suspend fun invalidateData() {
-            super.invalidateData()
-            manualBillDetails.value = null
-            transactions.value = null
-        }
-
-        suspend fun fetchManualBillDetails() = withContext(computationScope) {
-            val session = requireSession()
-            val response = client.get(manuaBillUrl) {
-                session.apply()
-            }
-            manualBillDetails.value = htmlDocument(response.bodyAsText()) {
-                scrapeManualBillDetails()
-            }
-            manualBillDetailsDirty = false
-        }
-
-        suspend fun fetchTransactions() {
-            val session = requireSession()
-            val response = client.get(transactionsUrl) {
-                session.apply()
-            }
-            transactions.value = htmlDocument(response.bodyAsText()) {
-                scrapeTransactions()
-            }
-            transactionsDirty = false
-        }
-
-        suspend fun submitCart(account: Account, cart: Cart) {
-            if (cart.isEmpty()) return
-            val session = requireSession()
-            client.submitForm(
-                url = "$manuaBillUrl",
-                formParameters = parameters {
-                    cart.forEach { (item, count) ->
-                        info { "adding @ $account : $item x $count" }
-                        append("tdata[account_id][]", account.id)
-                        append("tdata[item_id][]", item.id)
-                        append("tdata[item_count][]", "$count")
-                        append("manualbill", "")
-                        append("lockedfrom", "1")
-                    }
-                }
-            ) {
-                session.apply()
-            }
-            jsonDataDirty = true
-            manualBillDetailsDirty = true
-            transactionsDirty = true
-        }
-
-        private fun Doc.scrapeManualBillDetails(): ManualBillDetails = form {
-            table {
-                val accounts = select {
-                    withId = "manual_account_id"
-                    option {
-                        findAll {
-                            mapNotNull { option ->
-                                if (option.hasAttribute("disabled")) null
-                                else if (option.attribute("value").isBlank()) null
-                                else Account(
-                                    name = option.text,
-                                    id = option.attribute("value"),
-                                    isDefault = option.attribute("selected") == "selected"
-                                )
-                            }
-                        }
-                    }
-                }
-                val groups = select {
-                    withId = "manual_item_id"
-                    option {
-                        findAll {
-                            val groups = mutableListOf<ItemGroup>()
-                            var currentGroup: String? = null
-                            var currentItems = mutableListOf<Item>()
-                            for (option in this) {
-                                if (option.hasAttribute("disabled")) {
-                                    if (currentGroup != null) {
-                                        groups += ItemGroup(
-                                            currentGroup,
-                                            currentItems
-                                        )
-                                    }
-                                    currentGroup = option.text
-                                    currentItems = mutableListOf()
-                                } else if (option.attribute("value").isBlank()) continue
-                                else {
-                                    val id = option.attribute("value")
-                                    val name = option.text
-                                    val knownItems = KnownItem.byId[id]
-                                    if (knownItems != null) knownItems.forEach {
-                                        currentItems += Item(
-                                            name = name,
-                                            id = id,
-                                            knownItem = it
-                                        )
-                                    } else {
-                                        currentItems += Item(
-                                            name = option.text,
-                                            id = id
-                                        )
-                                    }
-                                }
-                            }
-                            if (currentGroup != null) {
-                                groups += ItemGroup(
-                                    currentGroup,
-                                    currentItems
-                                )
-                            }
-                            groups
-                        }
-                    }
-                }
-                ManualBillDetails(accounts, groups)
-            }
-        }
-
-        private fun Doc.scrapeTransactions(): List<Transaction> = table {
-            withClass = "transactions"
-            tr {
-                findAll {
-                    mapNotNull { row ->
-                        if (row.children.any { it.tagName == "th" }) return@mapNotNull null
-                        row.scrapeTransactionRow()
-                    }
-                }
-            }
-        }
-        private fun String.euros() = removeSuffix("€").toDouble()
-        private fun DocElement.scrapeTransactionRow(): Transaction? = td {
-            findAll {
-                if (size != 7) return@findAll null
-                // Example: 2024-02-26 13:05:21
-                val dateTime = LocalDateTime.parse(
-                    this[0].text.trim(),
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
-                )
-                val payer = this[1].text.trim().takeUnless { it == "-" }
-                val payee = this[2].text.trim()
-                val purpose = this[3].text.parseTransactionPurpose()
-                val total = this[4].text.trim().euros()
-                val newBalance = this[5].text.trim().euros()
-                val refundId = this[6].a { attribute("href") }.substringAfterLast("=")
-                Transaction(
-                    date = dateTime,
-                    payer = payer,
-                    payee = payee,
-                    purpose = purpose,
-                    total = total,
-                    newBalance = newBalance,
-                    refundId = refundId
-                )
-            }
-        }
-        private fun String.parseTransactionPurpose(): Transaction.Purpose {
-            val trimmed = trim()
-            return when (trimmed.lowercase()) {
-                "einzahlung" -> Transaction.Purpose.Deposit
-                else -> {
-                    val match = "([0-9]+)x (.+) à (-?[0-9.]+€?)".toRegex().matchEntire(trimmed)
-                        ?: return Transaction.Purpose.Other(trimmed)
-                    val (count, itemName, unitPrice) = match.destructured
-                    Transaction.Purpose.Purchase(itemName, count, unitPrice.euros())
-                }
-            }
-        }
-    }*/
-
-
-    /*inner class HiwiTracker : Module<MonthDataResponse>(Url("https://hiwi.embedded.rwth-aachen.de")) {
-
-        val dataByMonth = mutableStateMapOf<MonthKey, RichDataState<MonthDataResponse, List<String>>>()
-
-        private val apiDateFormat = LocalDate.Format {
-            year()
-            char('-')
-            monthNumber()
-            char('-')
-            dayOfMonth()
-        }
-        private val apiTimeFormat = LocalTime.Format {
-            hour()
-            char(':')
-            minute()
-        }
-        private fun Duration.apiFormat() = toComponents { hours, minutes, _, _ ->
-            "%02d:%02d".format(hours, minutes)
-        }
-
-        suspend fun fetchDataForMonth(month: MonthKey) {
-            val richDataState = dataByMonth.getOrPut(month) { RichDataState() }
-            richDataState.tryCalculate(
-                onError = {
-                    warn(it) { "Error fetching data for $month" }
-                    RichData.Error(emptyList())
-                }
-            ) {
-                val session = requireSession()
-                val response = client.get(jsonUrl) {
-                    session.apply()
-                    parameter("selectdate", month.toLocalDate().format(apiDateFormat))
-                }
-                RichData.Data(response.toData())
-            }
-        }
-
-        override suspend fun HttpResponse.toData(): MonthDataResponse {
-            try {
-                return body<MonthDataResponse>()
-            } catch (e: Throwable) {
-                val text = bodyAsText()
-                debug { text }
-                throw e
-            }
-        }
-        override suspend fun invalidateData() {
-            super.invalidateData()
-            dataByMonth.clear()
-        }
-
-        suspend fun submitWorkEntry(workEntry: WorkEntry) {
-            if (!workEntry.isValid) return
-            val monthKey = workEntry.date.toMonthKey()
-            val session = requireSession()
-            client.submitForm(
-                url = "$baseUrl",
-                formParameters = parameters {
-                    append("hw_date", workEntry.date.format(apiDateFormat))
-                    append("hw_begin", workEntry.begin.format(apiTimeFormat))
-                    append("hw_end", workEntry.end.format(apiTimeFormat))
-                    append("hw_breaktime", workEntry.breakDurationOrNull?.apiFormat() ?: "")
-                    append("hw_note", workEntry.note)
-                    append("savetimes", "")
-                }
-            ) {
-                session.apply()
-            }
-            jsonDataDirty = true
-            dataByMonth[monthKey]?.markDirty()
-        }
-
-        suspend fun deleteWorkEntry(workEntryId: Int) {
-            val session = requireSession()
-            client.submitForm(
-                url = "$baseUrl",
-                formParameters = parameters {
-                    append("deleteentry", "$workEntryId")
-                },
-                encodeInQuery = true
-            ) {
-                session.apply()
-            }
-            jsonDataDirty = true
-            dataByMonth.values.forEach { it.markDirty() }
-        }
-    }*/
-}
-
-
-
-
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 b0b1778f745acede17b78ee3e2dbee0ac63122c7..8611ae09f30bae6d02326488eaa8293359b8adec 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt
@@ -16,14 +16,14 @@ 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.DeviceCredentials
+import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
 import java.io.File
 
 @Serializable
 data class Settings(
     val themeMode: ThemeMode = ThemeMode.Dark,
     val autoLogin: Boolean = false,
-    @Deprecated("Use favoriteItemIds instead")
+    @Deprecated("Use favoriteItemIds instead", ReplaceWith("favoriteItemIds"))
     val favoriteItems: List<String> = emptyList(),
     val favoriteItemIds: List<Int> = favoriteItems.mapNotNull { it.toIntOrNull() },
     val lastSelectedModule: String? = null,
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/UpdateController.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/UpdateController.kt
index 3f550fc612a20359b277665341071329385a1d72..fabf2ec5343be44e7a1376b8631f8641383611b0 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/UpdateController.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/UpdateController.kt
@@ -18,7 +18,7 @@ import net.novagamestudios.common_utils.info
 import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.MainActivity
 import net.novagamestudios.kaffeekasse.UpdateReceiver
-import net.novagamestudios.kaffeekasse.model.AppRelease
+import net.novagamestudios.kaffeekasse.model.app.AppRelease
 import java.io.File
 import java.io.FileInputStream
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/HiwiTrackerRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/HiwiTrackerRepository.kt
similarity index 93%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/HiwiTrackerRepository.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/HiwiTrackerRepository.kt
index e121aa501a6b3cc236dd2f354b97f4af679b4ae1..21f151c64aee0fce26238c5657b57da29d68dd9d 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/HiwiTrackerRepository.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/HiwiTrackerRepository.kt
@@ -1,11 +1,11 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.i11
 
 import kotlinx.coroutines.CoroutineScope
 import net.novagamestudios.common_utils.Logger
 import net.novagamestudios.common_utils.debug
 import net.novagamestudios.kaffeekasse.api.hiwi_tracker.HiwiTrackerAPI
 import net.novagamestudios.kaffeekasse.api.hiwi_tracker.HiwiTrackerScraper
-import net.novagamestudios.kaffeekasse.api.hiwi_tracker.MonthDataResponse
+import net.novagamestudios.kaffeekasse.api.hiwi_tracker.model.MonthDataResponse
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.MonthKey
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.MonthKey.Companion.toMonthKey
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/KaffeekasseRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
similarity index 92%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/KaffeekasseRepository.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
index 34aad0d666cde9805df3fa04f676fab2eedd7f74..ee38cc77f1bd64b83f2fd1db5f4f02b7f850523d 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/KaffeekasseRepository.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
@@ -1,20 +1,20 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.i11
 
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import net.novagamestudios.common_utils.Logger
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.APIPurchaseAccount
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.APIPurchaseAccount
 import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseAPI
 import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseScraper
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.ExtendedUserInfo
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.ExtendedUserInfo
 import net.novagamestudios.kaffeekasse.data.category
 import net.novagamestudios.kaffeekasse.data.cleanName
 import net.novagamestudios.kaffeekasse.data.cleanProductName
 import net.novagamestudios.kaffeekasse.data.cleanVariantName
 import net.novagamestudios.kaffeekasse.data.drawableResource
-import net.novagamestudios.kaffeekasse.model.Device
-import net.novagamestudios.kaffeekasse.model.DeviceCredentials
+import net.novagamestudios.kaffeekasse.model.credentials.Device
+import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Account
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item
@@ -83,9 +83,11 @@ class KaffeekasseRepository(
         val baseItemGroups = scraper.manualBillDetails().itemGroups
         progress(1f / 3f)
 
-        if (!isDeviceLoggedIn) return@RichDataSource RichData.Data(StockImpl(
+        if (!isDeviceLoggedIn) return@RichDataSource RichData.Data(
+            StockImpl(
             itemGroups = baseItemGroups
-        ))
+        )
+        )
 
         val itemGroupsById: Map<Int?, MutableItemGroup> = baseItemGroups.map {
             MutableItemGroup(
@@ -102,7 +104,8 @@ class KaffeekasseRepository(
                 val knownItems = KnownItem.byId[item.id]?.takeUnless { it.isEmpty() }
                 if (knownItems == null) {
                     // Unknown item
-                    itemGroupsById[item.itemTypeId.id]?.items?.add(ItemImpl(
+                    itemGroupsById[item.itemTypeId]?.items?.add(
+                        ItemImpl(
                         id = item.id,
                         originalName = item.originalName,
                         category = ItemCategory.Other,
@@ -112,10 +115,12 @@ class KaffeekasseRepository(
                         estimatedPrice = item.price,
                         imageDrawable = null,
                         imageUrl = item.imageUrl
-                    ))
+                    )
+                    )
                 } else knownItems.forEach { known ->
                     // Combine with known values
-                    itemGroupsById[item.itemTypeId.id]?.items?.add(ItemImpl(
+                    itemGroupsById[item.itemTypeId]?.items?.add(
+                        ItemImpl(
                         id = item.id,
                         originalName = item.originalName,
                         category = known.category,
@@ -125,7 +130,8 @@ class KaffeekasseRepository(
                         estimatedPrice = item.price,
                         imageDrawable = known.drawableResource,
                         imageUrl = item.imageUrl
-                    ))
+                    )
+                    )
                 }
             }
             progress(3f / 3f)
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/PortalRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt
similarity index 90%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/PortalRepository.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt
index 51cbe57dc629878e059ec7ca5a7c4ad71c48425f..963cfd23d89f8ec73487ab6ae20f3caadc74c469 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/PortalRepository.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/PortalRepository.kt
@@ -1,11 +1,11 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.i11
 
 import net.novagamestudios.common_utils.Logger
 import net.novagamestudios.common_utils.info
-import net.novagamestudios.kaffeekasse.api.PortalClient
+import net.novagamestudios.kaffeekasse.api.portal.PortalClient
 import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseAPI
-import net.novagamestudios.kaffeekasse.model.Login
-import net.novagamestudios.kaffeekasse.model.isUser
+import net.novagamestudios.kaffeekasse.model.credentials.Login
+import net.novagamestudios.kaffeekasse.model.credentials.isUser
 import net.novagamestudios.kaffeekasse.util.mapState
 
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabPackageReleases.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabPackageReleases.kt
similarity index 89%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabPackageReleases.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabPackageReleases.kt
index cdfc38418fb0089c57390ce949aa060c7d9555ee..0c4189ebc1d4599d823b2313b392efa5015d1591 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabPackageReleases.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabPackageReleases.kt
@@ -1,4 +1,4 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.releases
 
 import kotlinx.coroutines.flow.MutableStateFlow
 import net.novagamestudios.common_utils.Logger
@@ -7,8 +7,8 @@ import net.novagamestudios.common_utils.info
 import net.novagamestudios.kaffeekasse.gitlab.GitLab
 import net.novagamestudios.kaffeekasse.gitlab.GitLabPackage
 import net.novagamestudios.kaffeekasse.gitlab.GitLabPackageFile
-import net.novagamestudios.kaffeekasse.model.AppRelease
-import net.novagamestudios.kaffeekasse.model.AppVersion
+import net.novagamestudios.kaffeekasse.model.app.AppRelease
+import net.novagamestudios.kaffeekasse.model.app.AppVersion
 
 
 class GitLabPackageReleases(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabReleases.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabReleases.kt
similarity index 96%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabReleases.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabReleases.kt
index ed18a6e68e4c4e291bf6998b3de09ad9215ffd0d..970b65fda7a276ab743f56c0cfe3741f1e46e5f5 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/GitLabReleases.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/GitLabReleases.kt
@@ -1,12 +1,12 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.releases
 
 import kotlinx.coroutines.flow.MutableStateFlow
 import net.novagamestudios.common_utils.Logger
 import net.novagamestudios.common_utils.debug
 import net.novagamestudios.common_utils.info
 import net.novagamestudios.kaffeekasse.gitlab.GitLab
-import net.novagamestudios.kaffeekasse.model.AppRelease
-import net.novagamestudios.kaffeekasse.model.AppVersion
+import net.novagamestudios.kaffeekasse.model.app.AppRelease
+import net.novagamestudios.kaffeekasse.model.app.AppVersion
 
 class GitLabReleases(
     private val gitLab: GitLab
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Releases.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/Releases.kt
similarity index 58%
rename from app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Releases.kt
rename to app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/Releases.kt
index ca5ba59be71b0ad375af3e464d19dca9abf2c2ab..8698e298d367bf95e809d6398bf665f30aa8ef95 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Releases.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/releases/Releases.kt
@@ -1,7 +1,7 @@
-package net.novagamestudios.kaffeekasse.repositories
+package net.novagamestudios.kaffeekasse.repositories.releases
 
 import kotlinx.coroutines.flow.MutableStateFlow
-import net.novagamestudios.kaffeekasse.model.AppRelease
+import net.novagamestudios.kaffeekasse.model.app.AppRelease
 
 interface Releases {
     val newerReleases: MutableStateFlow<List<AppRelease>?>
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 30d09652bc662d9c4cc53a0bced525d713d10b8f..48a307d23cfce72aa14392b2aae8e85d8bd07b14 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt
@@ -68,9 +68,9 @@ import net.novagamestudios.kaffeekasse.AppModule
 import net.novagamestudios.kaffeekasse.AppModules
 import net.novagamestudios.kaffeekasse.HiwiTrackerModule
 import net.novagamestudios.kaffeekasse.KaffeekasseModule
-import net.novagamestudios.kaffeekasse.model.isUser
+import net.novagamestudios.kaffeekasse.model.credentials.isUser
 import net.novagamestudios.kaffeekasse.repositories.MutableSettingsStore
-import net.novagamestudios.kaffeekasse.repositories.PortalRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository
 import net.novagamestudios.kaffeekasse.ui.hiwi_tracker.HiwiTrackerTopBarActions
 import net.novagamestudios.kaffeekasse.ui.hiwi_tracker.HiwiTrackerTopBarTitle
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.KaffeekasseTopBarActions
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 301e90a9514ff88641328ef9ea447bfa907bf02a..fa95cf1f92e2b7604913942c21e0e3969d7199cb 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Login.kt
@@ -80,18 +80,18 @@ import net.novagamestudios.common_utils.debug
 import net.novagamestudios.common_utils.info
 import net.novagamestudios.common_utils.toastShort
 import net.novagamestudios.common_utils.warn
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.BasicUserInfo
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.BasicUserInfo
 import net.novagamestudios.kaffeekasse.api.kaffeekasse.KaffeekasseAPI
 import net.novagamestudios.kaffeekasse.data.cleanName
-import net.novagamestudios.kaffeekasse.model.Device
-import net.novagamestudios.kaffeekasse.model.DeviceCredentials
-import net.novagamestudios.kaffeekasse.model.Login
-import net.novagamestudios.kaffeekasse.model.isUser
-import net.novagamestudios.kaffeekasse.model.isValid
+import net.novagamestudios.kaffeekasse.model.credentials.Device
+import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
+import net.novagamestudios.kaffeekasse.model.credentials.Login
+import net.novagamestudios.kaffeekasse.model.credentials.isUser
+import net.novagamestudios.kaffeekasse.model.credentials.isValid
 import net.novagamestudios.kaffeekasse.repositories.Credentials
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.repositories.MutableSettingsStore
-import net.novagamestudios.kaffeekasse.repositories.PortalRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.FailureRetryScreen
 import net.novagamestudios.kaffeekasse.ui.util.rememberSerializableState
 import net.novagamestudios.kaffeekasse.ui.util.trueWhileWithMutex
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 c8bd0f33b0c0ac0cf5fdb63c3cdd7fdf77fa856d..118ee55746a08738bfbd5bbd01f219b2b19b8cf7 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt
@@ -56,13 +56,12 @@ import net.novagamestudios.common_utils.compose.state.collectAsStateIn
 import net.novagamestudios.common_utils.toastLong
 import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.BuildConfig
-import net.novagamestudios.kaffeekasse.model.AppRelease
-import net.novagamestudios.kaffeekasse.model.AppVersion
-import net.novagamestudios.kaffeekasse.model.format
+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.MutableSettingsStore
-import net.novagamestudios.kaffeekasse.repositories.Releases
+import net.novagamestudios.kaffeekasse.repositories.releases.Releases
 import net.novagamestudios.kaffeekasse.repositories.UpdateController
 import net.novagamestudios.kaffeekasse.util.openInBrowser
 import java.time.format.DateTimeFormatter
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 fd0e7c24501c906dad0c329c37b2b3f2f2cb23f2..817ea05f2beda9e03fcb831cae0ee3545d2ab2f5 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/UserSelection.kt
@@ -36,8 +36,8 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.flowOn
 import net.novagamestudios.common_utils.compose.components.RowCenter
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.BasicUserInfo
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.BasicUserInfo
+import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.FailureRetryScreen
 import net.novagamestudios.kaffeekasse.ui.util.PullToRefreshBox
 import net.novagamestudios.kaffeekasse.ui.util.RichDataContent
@@ -95,6 +95,7 @@ fun UserSelection(
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
         val focusRequester = remember { FocusRequester() }
+        // TODO
         DockedSearchBar(
             query = vm.searchQuery,
             onQueryChange = { vm.searchQuery = it },
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/EnterWorkingHours.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/EnterWorkingHours.kt
index a2ccf5a6697bd576704a1d5c8142340ca83b0dae..75238324ea3df23d4e9ecac6ffa1fcd4e7baf32f 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/EnterWorkingHours.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/hiwi_tracker/EnterWorkingHours.kt
@@ -94,7 +94,7 @@ import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry.Companion.Mi
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry.Companion.invalidLargeBreak
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry.Companion.invalidSmallBreak
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry.Companion.isValid
-import net.novagamestudios.kaffeekasse.repositories.HiwiTrackerRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.HiwiTrackerRepository
 import net.novagamestudios.kaffeekasse.ui.theme.disabled
 import net.novagamestudios.kaffeekasse.ui.util.ClockFace
 import net.novagamestudios.kaffeekasse.ui.util.TimePickerState
@@ -548,7 +548,7 @@ private fun DurationInput(
             )
         },
         keyboardOptions = KeyboardOptions(
-            autoCorrect = false,
+            autoCorrectEnabled = false,
             keyboardType = KeyboardType.Number,
             imeAction = ImeAction.Done
         ),
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 4e664e75a13636fd29d569e32f7981cbd041262a..11423cd4153ad4a1ea5c2836f192e080525f70c3 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
@@ -89,12 +89,12 @@ import net.novagamestudios.common_utils.compose.components.RowCenter
 import net.novagamestudios.common_utils.debug
 import net.novagamestudios.common_utils.verbose
 import net.novagamestudios.kaffeekasse.App
-import net.novagamestudios.kaffeekasse.model.format
+import net.novagamestudios.kaffeekasse.model.date_time.format
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.MonthKey
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.MonthKey.Companion.toMonthKey
 import net.novagamestudios.kaffeekasse.model.hiwi_tracker.WorkEntry
-import net.novagamestudios.kaffeekasse.api.hiwi_tracker.MonthDataResponse
-import net.novagamestudios.kaffeekasse.repositories.HiwiTrackerRepository
+import net.novagamestudios.kaffeekasse.api.hiwi_tracker.model.MonthDataResponse
+import net.novagamestudios.kaffeekasse.repositories.i11.HiwiTrackerRepository
 import net.novagamestudios.kaffeekasse.repositories.MutableSettingsStore
 import net.novagamestudios.kaffeekasse.ui.AppInfoTopBarAction
 import net.novagamestudios.kaffeekasse.ui.AppModuleSelection
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Account.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Account.kt
index 6a1086f8941807ec00323e06fa62a325b305336a..37fee4febb410d9015f1117705c63c76c8276bfb 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Account.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/Account.kt
@@ -34,7 +34,7 @@ import net.novagamestudios.common_utils.compose.components.ColumnCenter
 import net.novagamestudios.common_utils.format
 import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Account
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.FailureRetryScreen
 import net.novagamestudios.kaffeekasse.ui.util.PullToRefreshBox
 import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateHere
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 4cfeae1a561d607d77cab2fb2ec06c1e6be2382c..84250d1e8a81d5d0639d693499339f725f1ee958 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
@@ -20,7 +20,7 @@ import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.KaffeekasseModule
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty
-import net.novagamestudios.kaffeekasse.repositories.PortalRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository
 import net.novagamestudios.kaffeekasse.ui.AppBackNavigation
 import net.novagamestudios.kaffeekasse.ui.AppInfoTopBarAction
 import net.novagamestudios.kaffeekasse.ui.AppModuleSelection
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt
index 5c851c100ef4873db0a9aaaf228a16457dcb69f2..7bd1df06477b069d0cac435341f9c46d6ca4cf10 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt
@@ -38,7 +38,7 @@ import net.novagamestudios.common_utils.compose.tabIndicatorOffset
 import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.KaffeekasseModule
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.APICheckoutViewModel
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItems
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItemsViewModel
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 20f0f723266949571af24f0a092ee2013e50cfff..fdbb6c46fb777d9a775ad59be1eef13506f1f5c8 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
@@ -46,7 +46,7 @@ import net.novagamestudios.kaffeekasse.App
 import net.novagamestudios.kaffeekasse.data.category
 import net.novagamestudios.kaffeekasse.data.cleanFullName
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+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
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
index 03ad3955e9d789a64a5bb01e07346fd862cfaca1..d358d2d027ce01f482f4aa6db59c9607e016e72e 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
@@ -6,6 +6,7 @@ import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
 import androidx.compose.material3.Icon
+import androidx.compose.material3.MenuAnchorType
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -40,7 +41,7 @@ fun AccountSelection(
             state.selectedAccount?.name ?: "",
             onValueChange = { },
             Modifier
-                .menuAnchor(),
+                .menuAnchor(MenuAnchorType.PrimaryNotEditable),
             readOnly = true,
             label = { Text("Konto") },
             leadingIcon = { Icon(Icons.Default.Person, "Konto") },
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 003a3c348384f1ed853b079b6f5232550f1e601d..d6f5033d1c153f92b350b3e7ca412f883d0bb341 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
@@ -54,14 +54,14 @@ import net.novagamestudios.common_utils.compose.maskedCircleIcon
 import net.novagamestudios.common_utils.compose.state.ReentrantActionState
 import net.novagamestudios.common_utils.toastLong
 import net.novagamestudios.common_utils.warn
-import net.novagamestudios.kaffeekasse.api.kaffeekasse.APIPurchaseAccount
+import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.APIPurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty
-import net.novagamestudios.kaffeekasse.repositories.KaffeekasseRepository
+import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryIcon
 import net.novagamestudios.kaffeekasse.util.richdata.RichDataFlow
 import net.novagamestudios.kaffeekasse.util.richdata.RichDataState
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 4a5b5387148f0d2f46eebf7684bb9bb0ee61379b..5048dcc67f6c0899b6ec09f9d286faa3ab3fdec1 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
@@ -38,7 +38,7 @@ 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.KaffeekasseRepository
+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
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PagerState.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PagerState.kt
index 8b3478ae0537dba5d731e6d4f93c57b5328b6c11..433f6469366f43d3392b69eba3e00d1f510b6ba0 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PagerState.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PagerState.kt
@@ -3,6 +3,7 @@ package net.novagamestudios.kaffeekasse.ui.util
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.spring
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.TargetedFlingBehavior
 import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.pager.PageSize
@@ -57,15 +58,16 @@ fun <K : Any> HorizontalKeyedPager(
     modifier: Modifier = Modifier,
     contentPadding: PaddingValues = PaddingValues(0.dp),
     pageSize: PageSize = PageSize.Fill,
-    beyondBoundsPageCount: Int = PagerDefaults.BeyondBoundsPageCount,
+    beyondViewportPageCount: Int = PagerDefaults.BeyondViewportPageCount,
     pageSpacing: Dp = 0.dp,
     verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
-    flingBehavior: SnapFlingBehavior = PagerDefaults.flingBehavior(state = state),
+    flingBehavior: TargetedFlingBehavior = PagerDefaults.flingBehavior(state = state),
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
-    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
-        PagerDefaults.pageNestedScrollConnection(state, Orientation.Horizontal)
-    },
+    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
+        state,
+        Orientation.Horizontal
+    ),
     pageContent: @Composable PagerScope.(page: K) -> Unit
 ) {
     androidx.compose.foundation.pager.HorizontalPager(
@@ -73,7 +75,7 @@ fun <K : Any> HorizontalKeyedPager(
         modifier = modifier,
         contentPadding = contentPadding,
         pageSize = pageSize,
-        beyondBoundsPageCount = beyondBoundsPageCount,
+        beyondViewportPageCount = beyondViewportPageCount,
         pageSpacing = pageSpacing,
         verticalAlignment = verticalAlignment,
         flingBehavior = flingBehavior,
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PullToRefreshBox.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PullToRefreshBox.kt
index 859fd8ec72c6fc3658aa3c976787bae89ee1356c..0f75704cd761fc501df3c7d3b5ed16294c3c5fc7 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PullToRefreshBox.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/PullToRefreshBox.kt
@@ -105,7 +105,7 @@ internal class PullToRefreshStateImpl(
         ): Offset = when {
             !enabled() -> Offset.Zero
             // Swiping up
-            source == NestedScrollSource.Drag && available.y < 0 -> {
+            source == NestedScrollSource.UserInput && available.y < 0 -> {
                 consumeAvailableOffset(available)
             }
             else -> Offset.Zero
@@ -118,7 +118,7 @@ internal class PullToRefreshStateImpl(
         ): Offset = when {
             !enabled() -> Offset.Zero
             // Swiping down
-            source == NestedScrollSource.Drag && available.y > 0 -> {
+            source == NestedScrollSource.UserInput && available.y > 0 -> {
                 consumeAvailableOffset(available)
             }
             else -> Offset.Zero
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFlow.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFlow.kt
index 834bb47f14f278e5177bd62bbda21f9ddacd8728..71c1a3a27970700897ae367854a1c6cde4e1998f 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFlow.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFlow.kt
@@ -1,6 +1,7 @@
 package net.novagamestudios.kaffeekasse.util.richdata
 
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -52,6 +53,7 @@ fun <T : Any, R : Any> RichDataFlow<T>.mapRich(
     })
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @Suppress("UNCHECKED_CAST")
 fun <T : Any, R : Any> RichDataFlow<T>.mapLatestRich(
     loadDuringTransform: Boolean = false,
diff --git a/build.gradle.kts b/build.gradle.kts
index de030ed4a3fbfc299fa38a9a4696002c50584381..7c081188a34d9dee53a4541d911d2689c0b27156 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,6 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 plugins {
+    alias(libs.plugins.gradle.versions) apply false
     alias(libs.plugins.android.application) apply false
     alias(libs.plugins.kotlin.android) apply false
     alias(libs.plugins.kotlinx.serialization) apply false
diff --git a/gradle.properties b/gradle.properties
index 3c5031eb7d63f785752b1914cc8692a453d1cc63..dc6773a6db44f3173d3c4e299a6cccf7c609f812 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,16 +1,6 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
 # Specifies the JVM arguments used for the daemon process.
 # The setting is particularly useful for tweaking memory settings.
 org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
 # AndroidX package structure to make it clearer which packages are bundled with the
 # Android operating system, and which are packaged with your app's APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
@@ -20,4 +10,12 @@ kotlin.code.style=official
 # Enables namespacing of each library's R class so that its R class includes only the
 # resources declared in the library itself and none from the library's dependencies,
 # thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+
+#kotlin.experimental.tryK2=true
+
+# See https://github.com/ben-manes/gradle-versions-plugin/issues/859
+# Remove when version of AGP >= 8.3.1
+systemProp.javax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
+systemProp.javax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
+systemProp.javax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0a4f1e8d01dc7167c5487c99e52d4e6f6c5d0af0..f8bf34cc13f4117925a2b895b1626174cf95795c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Nov 27 19:34:08 CET 2023
+#Thu Apr 18 16:02:39 CEST 2024
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a59b93ec321a8d8a339cb2e43b52964047b404c5..84e0f446984555ebf00037472fd7f2714c3d07b4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -15,41 +15,44 @@ dependencyResolutionManagement {
     }
     versionCatalogs {
         create("libs") {
-            version("kotlin", "1.9.10")
-            version("compose-bom", "2024.02.01")
-            version("androidx-lifecycle", "2.7.0")
-            version("ktor", "2.3.8")
+            version("kotlin", "1.9.23")
+            version("compose-compiler", "1.5.12")
+            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("vico", "2.0.0-alpha.8")
-            version("voyager", "1.0.0")
+            version("voyager", "1.1.0-alpha04")
             version("coil", "2.6.0")
 
 
+            plugin("gradle-versions", "com.github.ben-manes.versions").version("0.51.0")
             plugin("android-application", "com.android.application").version("8.2.0")
             plugin("kotlin-android", "org.jetbrains.kotlin.android").versionRef("kotlin")
             plugin("kotlinx-serialization", "org.jetbrains.kotlin.plugin.serialization").versionRef("kotlin")
 
 
-            library("androidx-core", "androidx.core", "core-ktx").version("1.12.0")
+            library("androidx-core", "androidx.core", "core-ktx").version("1.13.0")
             library("androidx-lifecycle-runtime", "androidx.lifecycle", "lifecycle-runtime-ktx").versionRef("androidx-lifecycle")
             library("androidx-lifecycle-viewmodel-compose", "androidx.lifecycle", "lifecycle-viewmodel-compose").versionRef("androidx-lifecycle")
 
             library("androidx-navigation-compose", "androidx.navigation", "navigation-compose").version("2.7.7")
-            library("androidx-activity-compose", "androidx.activity", "activity-compose").version("1.8.2")
+            library("androidx-activity-compose", "androidx.activity", "activity-compose").version("1.9.0")
 
             library("compose-bom", "androidx.compose", "compose-bom").versionRef("compose-bom")
             library("compose-ui", "androidx.compose.ui", "ui").withoutVersion()
             library("compose-ui-graphics", "androidx.compose.ui", "ui-graphics").withoutVersion()
             library("compose-ui-tooling-preview", "androidx.compose.ui", "ui-tooling-preview").withoutVersion()
-            library("compose-ui-text-google-fonts", "androidx.compose.ui", "ui-text-google-fonts").version("1.6.2")
-            library("compose-material3", "androidx.compose.material3", "material3").version("1.2.0")
-            library("compose-material-icons-extended", "androidx.compose.material", "material-icons-extended").version("1.6.2")
-            library("compose-grid", "io.woong.compose.grid", "grid").version("1.2.1")
+            library("compose-ui-text-google-fonts", "androidx.compose.ui", "ui-text-google-fonts").withoutVersion() // .version("1.6.2")
+            library("compose-material-icons-extended", "androidx.compose.material", "material-icons-extended").withoutVersion() // .version("1.7.0-alpha07")
+            library("compose-material3", "androidx.compose.material3", "material3").versionRef("material3")
+            library("compose-grid", "io.woong.compose.grid", "grid").version("1.2.2")
 
-            library("androidx-credentials", "androidx.credentials", "credentials").version("1.3.0-alpha01")
+            library("androidx-credentials", "androidx.credentials", "credentials").version("1.3.0-alpha03")
             library("androidx-datastore", "androidx.datastore", "datastore-preferences-android").version("1.1.0-alpha07")
 
             library("kotlinx-datetime", "org.jetbrains.kotlinx", "kotlinx-datetime").version("0.6.0-RC.2")
-            library("kotlinx-serialization-json", "org.jetbrains.kotlinx", "kotlinx-serialization-json").version("1.6.0")
+            library("kotlinx-serialization-json", "org.jetbrains.kotlinx", "kotlinx-serialization-json").version("1.6.3")
 
             library("ktor-client-core", "io.ktor", "ktor-client-core").versionRef("ktor")
             library("ktor-client-okhttp", "io.ktor", "ktor-client-okhttp").versionRef("ktor")
@@ -73,7 +76,7 @@ dependencyResolutionManagement {
             library("coil", "io.coil-kt", "coil").versionRef("coil")
             library("coil-compose", "io.coil-kt", "coil-compose").versionRef("coil")
 
-            library("commonutils", "com.gitlab.JojoIV", "common_utils").version("6e8e35fce9")
+            library("commonutils", "com.gitlab.JojoIV", "common_utils").version("2d5e5c9a17")
         }
     }
 }