diff --git a/api/src/main/kotlin/de/chaosdorf/mete/AuditEntryId.kt b/api/src/main/kotlin/de/chaosdorf/mete/AuditEntryId.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d1725c4307a22b314cc66aeccdac97980edc0c69
--- /dev/null
+++ b/api/src/main/kotlin/de/chaosdorf/mete/AuditEntryId.kt
@@ -0,0 +1,31 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.mete
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+@JvmInline
+value class AuditEntryId(val value: Long)
diff --git a/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditEntryModelV1.kt b/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditEntryModelV1.kt
index 8ba703392c092f96a4e70d126a34507e513b8905..b3cf90126b925d431e3a6d6b5c38090482e6c1c3 100644
--- a/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditEntryModelV1.kt
+++ b/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditEntryModelV1.kt
@@ -24,13 +24,17 @@
 
 package de.chaosdorf.mete.v1
 
+import de.chaosdorf.mete.AuditEntryId
 import de.chaosdorf.mete.DrinkId
-import de.chaosdorf.mete.UserId
 import kotlinx.datetime.Instant
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
 
+@Serializable
 data class AuditEntryModelV1(
+  val id: AuditEntryId,
+  @SerialName("created_at")
+  val createdAt: Instant,
   val difference: Double,
-  val drink: DrinkId,
-  val user: UserId,
-  val createdAt: Instant
+  val drink: DrinkId?
 )
diff --git a/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditResponseV1.kt b/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditResponseV1.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c0d79e1746447b91a38fe269eafb78472f6990bc
--- /dev/null
+++ b/api/src/main/kotlin/de/chaosdorf/mete/v1/AuditResponseV1.kt
@@ -0,0 +1,40 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.mete.v1
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AuditResponseV1(
+  @SerialName("payments_sum")
+  val payments: Double,
+  @SerialName("deposits_sum")
+  val deposits: Double,
+  @SerialName("sum")
+  val total: Double,
+  @SerialName("audits")
+  val entries: List<AuditEntryModelV1>
+)
diff --git a/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt b/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
index 0a1b1ee34a1c7568fcf9c1c2546aac1cce7eeae8..d08da96ac03856aa36bc128445742e2f17e238cf 100644
--- a/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
+++ b/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
@@ -29,13 +29,16 @@ import de.chaosdorf.mete.PwaManifest
 import de.chaosdorf.mete.UserId
 import retrofit2.http.GET
 import retrofit2.http.Path
+import retrofit2.http.Query
 
 interface MeteApiV1 {
   @GET("manifest.json")
   suspend fun getManifest(): PwaManifest?
 
   @GET("api/v1/audits.json")
-  suspend fun getAudits(): List<AuditEntryModelV1>
+  suspend fun getAudits(
+    @Query("user") user: Long? = null
+  ): AuditResponseV1
 
   @GET("api/v1/barcodes.json")
   suspend fun listBarcodes(): List<BarcodeModelV1>
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1efc1531328c9883640ef4b1e792d70d2eb1f811..e68570b35f3d3f16626d1d7b054277297ab2eba8 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -56,6 +56,9 @@ android {
 
 dependencies {
   implementation(libs.kotlin.stdlib)
+  implementation(libs.kotlinx.datetime)
+  implementation(libs.kotlinx.serialization.json)
+  coreLibraryDesugaring(libs.desugar.jdk)
 
   implementation(libs.kotlinx.coroutines.android)
   testImplementation(libs.kotlinx.coroutines.test)
@@ -86,14 +89,13 @@ dependencies {
   implementation(libs.androidx.navigation.compose)
 
   implementation(libs.okhttp)
-  implementation(libs.kotlinx.serialization.json)
   implementation(libs.coil.compose)
 
   implementation(libs.hilt.navigation)
   implementation(libs.hilt.android)
   ksp(libs.hilt.compiler)
 
-  implementation("androidx.datastore:datastore-preferences:1.0.0")
+  implementation(libs.androidx.datastore.preferences)
 
   implementation(project(":api"))
   implementation(project(":persistence"))
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/di/DatabaseModule.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/di/DatabaseModule.kt
index a8606f1c8d59c667229468ca6ac15eecd8f955f0..8d35e31128fafdc943f6f8be158020b3f0522f2c 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/di/DatabaseModule.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/di/DatabaseModule.kt
@@ -33,6 +33,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.components.SingletonComponent
 import de.chaosdorf.meteroid.MeteroidDatabase
 import de.chaosdorf.meteroid.model.DrinkRepository
+import de.chaosdorf.meteroid.model.PurchaseRepository
 import de.chaosdorf.meteroid.model.ServerRepository
 import de.chaosdorf.meteroid.model.UserRepository
 import javax.inject.Singleton
@@ -59,6 +60,11 @@ object DatabaseModule {
     database: MeteroidDatabase
   ): UserRepository = database.users()
 
+  @Provides
+  fun providePurchaseRepository(
+    database: MeteroidDatabase
+  ): PurchaseRepository = database.purchases()
+
   @Provides
   fun provideServerRepository(
     database: MeteroidDatabase
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sample/SampleDrinkProvider.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sample/SampleDrinkProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c29e736c162cbb4082ca818151c0415667082468
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sample/SampleDrinkProvider.kt
@@ -0,0 +1,68 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.sample
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import de.chaosdorf.mete.DrinkId
+import de.chaosdorf.meteroid.model.Drink
+import de.chaosdorf.meteroid.model.ServerId
+import kotlinx.datetime.Instant
+
+class SampleDrinkProvider : PreviewParameterProvider<Drink> {
+  override val values = sequenceOf(
+      Drink(
+          serverId = ServerId(-1),
+          drinkId = DrinkId(27),
+          active = true,
+          name = "Club Mate",
+          volume = 0.5,
+          caffeine = null,
+          price = 1.5,
+          createdAt = Instant.fromEpochMilliseconds(1684598011800),
+          updatedAt = Instant.fromEpochMilliseconds(1684607122132),
+          logoUrl = "http://192.168.188.36:8080/system/drinks/logos/000/000/027/thumb/logo.png",
+          logoFileName = "logo.png",
+          logoContentType = "image/png",
+          logoFileSize = 183063,
+          logoUpdatedAt = Instant.fromEpochMilliseconds(1684607121995)
+      ),
+      Drink(
+          serverId = ServerId(-1),
+          drinkId = DrinkId(15),
+          active = false,
+          name = "Paulaner Spezi",
+          volume = 0.5,
+          caffeine = null,
+          price = 1.5,
+          createdAt = Instant.fromEpochMilliseconds(1684597806099),
+          updatedAt = Instant.fromEpochMilliseconds(1684607346944),
+          logoUrl = "http://192.168.188.36:8080/system/drinks/logos/000/000/015/thumb/logo.png",
+          logoFileName = "logo.png",
+          logoContentType = "image/png",
+          logoFileSize = 173265,
+          logoUpdatedAt = Instant.fromEpochMilliseconds(1684607346835)
+      )
+  )
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/AccountProvider.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/AccountProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4420a5eda68e7e94a37a2ce7b38f7a9e4ca4294b
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/AccountProvider.kt
@@ -0,0 +1,60 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.sync
+
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.ServerRepository
+import de.chaosdorf.meteroid.model.User
+import de.chaosdorf.meteroid.model.UserRepository
+import de.chaosdorf.meteroid.storage.AccountPreferences
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import javax.inject.Inject
+
+class AccountProvider @Inject constructor(
+  accountPreferences: AccountPreferences,
+  serverRepository: ServerRepository,
+  userRepository: UserRepository,
+) {
+  val account: Flow<Pair<Server, User?>?> =
+    accountPreferences.state.flatMapLatest { preferences ->
+      if (preferences.server == null) {
+        flowOf(null)
+      } else {
+        serverRepository.getFlow(preferences.server).flatMapLatest { server ->
+          if (server == null) {
+            flowOf(null)
+          } else if (preferences.user == null) {
+            flowOf(Pair(server, null))
+          } else {
+            userRepository.getFlow(server.serverId, preferences.user)
+              .mapLatest { user -> Pair(server, user) }
+          }
+        }
+      }
+    }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/DrinkSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
similarity index 95%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/storage/DrinkSyncHandler.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
index 732f3ed1c05d366481e12a247e172f096bd3fee1..dc8e5ed99634ea494b8f9269b6255cb4bd049732 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/DrinkSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
@@ -22,7 +22,7 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.storage
+package de.chaosdorf.meteroid.sync
 
 import androidx.room.withTransaction
 import de.chaosdorf.mete.DrinkId
@@ -59,6 +59,6 @@ class DrinkSyncHandler @Inject constructor(
   override suspend fun loadCurrent(context: Server): List<Drink> {
     val api = MeteApiV1Factory.newInstance(context.url)
     val loadedEntries = api.listDrinks()
-    return loadedEntries.map { Drink.fromModelV1(context.serverId, it) }
+    return loadedEntries.map { Drink.fromModelV1(context, it) }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8ba6652d07b5d7958abab18f18b3813d4ce9fe2b
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt
@@ -0,0 +1,66 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.sync
+
+import androidx.room.withTransaction
+import de.chaosdorf.mete.AuditEntryId
+import de.chaosdorf.mete.UserId
+import de.chaosdorf.mete.v1.MeteApiV1Factory
+import de.chaosdorf.meteroid.MeteroidDatabase
+import de.chaosdorf.meteroid.model.Purchase
+import de.chaosdorf.meteroid.model.PurchaseRepository
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.ServerId
+import javax.inject.Inject
+
+class PurchaseSyncHandler @Inject constructor(
+  private val db: MeteroidDatabase,
+  private val repository: PurchaseRepository
+) : SyncHandler<Pair<Server, UserId>, Purchase, PurchaseSyncHandler.Key>() {
+  data class Key(
+    val server: ServerId, val purchase: AuditEntryId
+  )
+
+  override suspend fun withTransaction(block: suspend () -> Unit) =
+    db.withTransaction(block)
+
+  override suspend fun store(entry: Purchase) =
+    repository.save(entry)
+
+  override suspend fun delete(key: Key) =
+    repository.delete(key.server, key.purchase)
+
+  override fun entryToKey(entry: Purchase) = Key(entry.serverId, entry.purchaseId)
+
+  override suspend fun loadStored(context: Pair<Server, UserId>): List<Purchase> =
+    repository.getAll(context.first.serverId, context.second)
+
+  override suspend fun loadCurrent(context: Pair<Server, UserId>): List<Purchase> {
+    val (server, userId) = context
+    val api = MeteApiV1Factory.newInstance(server.url)
+    val loadedEntries = api.getAudits(user = userId.value).entries
+    return loadedEntries.map { Purchase.fromModelV1(server, userId, it) }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/SyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt
similarity index 98%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/storage/SyncHandler.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt
index 40be77a41d187054aa92a1b86423ee6d6010a1fd..6bce3403a0f01c15166d0b1f67a99ecd70bccf4b 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/SyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt
@@ -22,7 +22,7 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.storage
+package de.chaosdorf.meteroid.sync
 
 import android.util.Log
 import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5ae0c58b4528f76c57d8fad51672d29a0ac33e49
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncViewModel.kt
@@ -0,0 +1,35 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.sync
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class SyncViewModel @Inject constructor(
+) : ViewModel() {
+
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/UserSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
similarity index 95%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/storage/UserSyncHandler.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
index 906771a524a782b8a3d58383beb3b849d737890c..a6f9291d3db387c6737d8f3f1ba64ca03d320124 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/UserSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
@@ -22,7 +22,7 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.storage
+package de.chaosdorf.meteroid.sync
 
 import androidx.room.withTransaction
 import de.chaosdorf.mete.UserId
@@ -59,6 +59,6 @@ class UserSyncHandler @Inject constructor(
   override suspend fun loadCurrent(context: Server): List<User> {
     val api = MeteApiV1Factory.newInstance(context.url)
     val loadedEntries = api.listUsers()
-    return loadedEntries.map { User.fromModelV1(context.serverId, it) }
+    return loadedEntries.map { User.fromModelV1(context, it) }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppRouter.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppRouter.kt
index b22f4ce8f05065532a28afbe460fdedd28ad1633..81b5e89523a17d84e13df25d394e7c4cc2650732 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppRouter.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppRouter.kt
@@ -25,23 +25,28 @@
 package de.chaosdorf.meteroid.ui
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
+import androidx.compose.ui.Alignment
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
-import de.chaosdorf.meteroid.ui.home.DrinkListScreen
-import de.chaosdorf.meteroid.ui.home.DrinkListViewModel
-import de.chaosdorf.meteroid.ui.home.HomeSections
+import de.chaosdorf.meteroid.ui.drinks.DrinkListScreen
+import de.chaosdorf.meteroid.ui.drinks.DrinkListViewModel
+import de.chaosdorf.meteroid.ui.money.MoneyListScreen
+import de.chaosdorf.meteroid.ui.money.MoneyListViewModel
+import de.chaosdorf.meteroid.ui.navigation.Routes
+import de.chaosdorf.meteroid.ui.purchases.PurchaseListScreen
+import de.chaosdorf.meteroid.ui.purchases.PurchaseViewModel
 import de.chaosdorf.meteroid.ui.servers.AddServerScreen
 import de.chaosdorf.meteroid.ui.servers.ServerListScreen
 import de.chaosdorf.meteroid.ui.users.UserListScreen
@@ -65,8 +70,11 @@ fun AppRouter(viewModel: AppViewModel = viewModel()) {
 
   NavHost(navController, startDestination = Routes.Init) {
     composable(route = Routes.Init) { _ ->
-      Box {
-        Text("Loading")
+      Box(contentAlignment = Alignment.Center) {
+        Column {
+          CircularProgressIndicator()
+          Text("Loading")
+        }
       }
     }
 
@@ -116,26 +124,15 @@ fun AppRouter(viewModel: AppViewModel = viewModel()) {
     navigation(route = Routes.Home.Root, startDestination = Routes.Home.Purchase) {
       composable(Routes.Home.Purchase) { _ ->
         val drinkListViewModel = hiltViewModel<DrinkListViewModel>()
-        MeteroidScaffold(
-          routes = HomeSections.entries,
-          currentRoute = HomeSections.PURCHASE,
-          navigateTo = navController::navigate,
-          onBack = { navController.navigate(Routes.Users.Root) },
-        ) { paddingValues ->
-          DrinkListScreen(drinkListViewModel, Modifier.padding(paddingValues))
-        }
+        DrinkListScreen(drinkListViewModel, navController::navigate)
       }
       composable(Routes.Home.Deposit) { _ ->
-        MeteroidScaffold(
-          routes = HomeSections.entries,
-          currentRoute = HomeSections.DEPOSIT,
-          navigateTo = navController::navigate,
-          onBack = { navController.navigate(Routes.Users.Root) },
-        ) { paddingValues ->
-          Box(Modifier.padding(paddingValues)) {
-            Text("TODO: Deposit")
-          }
-        }
+        val moneyListViewModel = hiltViewModel<MoneyListViewModel>()
+        MoneyListScreen(moneyListViewModel, navController::navigate)
+      }
+      composable(Routes.Home.History) { _ ->
+        val purchaseViewModel = hiltViewModel<PurchaseViewModel>()
+        PurchaseListScreen(purchaseViewModel, navController::navigate)
       }
     }
   }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
index 3781fd46a11af9cb723face86a61c2fe8a952fa3..bd066bb443087ce79a3443a72e69524010a02fc5 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
@@ -32,12 +32,14 @@ import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerId
 import de.chaosdorf.meteroid.model.ServerRepository
 import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.storage.DrinkSyncHandler
-import de.chaosdorf.meteroid.storage.UserSyncHandler
+import de.chaosdorf.meteroid.sync.AccountProvider
+import de.chaosdorf.meteroid.sync.DrinkSyncHandler
+import de.chaosdorf.meteroid.sync.PurchaseSyncHandler
+import de.chaosdorf.meteroid.sync.UserSyncHandler
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
@@ -46,10 +48,12 @@ import javax.inject.Inject
 
 @HiltViewModel
 class AppViewModel @Inject constructor(
+  accountProvider: AccountProvider,
   private val accountPreferences: AccountPreferences,
   private val serverRepository: ServerRepository,
   private val userSyncHandler: UserSyncHandler,
   private val drinkSyncHandler: DrinkSyncHandler,
+  private val purchaseSyncHandler: PurchaseSyncHandler
 ) : ViewModel() {
   val initState: StateFlow<InitState> = accountPreferences.state
     .flatMapLatest { preferences ->
@@ -63,17 +67,14 @@ class AppViewModel @Inject constructor(
         }
     }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), InitState.LOADING)
 
-  private val server: StateFlow<Server?> =
-    accountPreferences.state.flatMapLatest { preferences ->
-      if (preferences.server == null) flowOf(null)
-      else serverRepository.getFlow(preferences.server)
-    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
-
   init {
-    server.onEach { server ->
-      if (server != null) {
+    accountProvider.account.distinctUntilChanged().onEach { account ->
+      account?.let { (server, user) ->
         userSyncHandler.sync(server)
         drinkSyncHandler.sync(server)
+        user?.let { user ->
+          purchaseSyncHandler.sync(Pair(server, user.userId))
+        }
       }
     }.launchIn(viewModelScope)
   }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListScreen.kt
new file mode 100644
index 0000000000000000000000000000000000000000..46b2833374568b31f86fee4ff18f620c34a2577a
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListScreen.kt
@@ -0,0 +1,80 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.drinks
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import de.chaosdorf.meteroid.sync.SyncHandler
+import de.chaosdorf.meteroid.ui.navigation.HomeSections
+import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
+import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
+
+@Composable
+fun DrinkListScreen(
+  viewModel: DrinkListViewModel,
+  onNavigate: (String) -> Unit = {}
+) {
+  val account by viewModel.account.collectAsState()
+  val drinks by viewModel.drinks.collectAsState()
+  val syncState by viewModel.syncState.collectAsState()
+  Scaffold(
+    topBar = { MeteroidTopBar(account, onNavigate) },
+    bottomBar = {
+      MeteroidBottomBar(
+        currentRoute = HomeSections.PURCHASE,
+        historyEnabled = account?.second?.audit == true,
+        navigateTo = onNavigate
+      )
+    }
+  ) { paddingValues: PaddingValues ->
+    Column {
+      if (syncState == SyncHandler.State.Loading) {
+        LinearProgressIndicator()
+      }
+      LazyVerticalGrid(
+        GridCells.Adaptive(120.dp),
+        modifier = Modifier.padding(paddingValues),
+        contentPadding = PaddingValues(12.dp),
+        horizontalArrangement = Arrangement.SpaceBetween,
+      ) {
+        items(drinks) { drink ->
+          DrinkTile(drink)
+        }
+      }
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
similarity index 67%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListViewModel.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
index 331771d492ce0a43d5c8577778e509e9b1c12efa..1a10f7dc1d592371aa20107ca4c40d40207a601f 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
@@ -22,37 +22,40 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home
+package de.chaosdorf.meteroid.ui.drinks
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
 import de.chaosdorf.meteroid.model.Drink
 import de.chaosdorf.meteroid.model.DrinkRepository
-import de.chaosdorf.meteroid.model.ServerId
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.storage.DrinkSyncHandler
-import kotlinx.coroutines.flow.Flow
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.User
+import de.chaosdorf.meteroid.sync.AccountProvider
+import de.chaosdorf.meteroid.sync.DrinkSyncHandler
+import de.chaosdorf.meteroid.sync.SyncHandler
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import javax.inject.Inject
 
 @HiltViewModel
 class DrinkListViewModel @Inject constructor(
-  drinkRepository: DrinkRepository,
-  accountPreferences: AccountPreferences,
+  accountProvider: AccountProvider,
+  repository: DrinkRepository,
   syncHandler: DrinkSyncHandler
 ) : ViewModel() {
-  private val serverId: Flow<ServerId?> = accountPreferences.state.mapLatest { it.server }
-  val drinks: StateFlow<List<Drink>> = serverId.flatMapLatest {
-    it?.let { serverId ->
-      drinkRepository.getAllFlow(serverId)
-    } ?: flowOf(emptyList())
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
+  val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
+    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
 
-  val syncState = syncHandler.state
+  val drinks: StateFlow<List<Drink>> = accountProvider.account
+    .flatMapLatest { account ->
+      account?.let { (server, _) ->
+        repository.getAllFlow(server.serverId)
+      } ?: flowOf(emptyList())
+    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
+
+  val syncState: StateFlow<SyncHandler.State> = syncHandler.state
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2099f684419e787c81d533635ac1f1d1f5c170af
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt
@@ -0,0 +1,112 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.drinks
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import coil.compose.rememberAsyncImagePainter
+import de.chaosdorf.meteroid.model.Drink
+import de.chaosdorf.meteroid.sample.SampleDrinkProvider
+
+@Preview(widthDp = 120, showBackground = true)
+@Composable
+fun DrinkTile(
+  @PreviewParameter(SampleDrinkProvider::class) item: Drink
+) {
+  val thumbPainter = rememberAsyncImagePainter(
+    item.logoUrl
+  )
+  val drinkPainter = rememberAsyncImagePainter(
+    item.logoUrl.replace("/thumb/", "/original/"),
+    error = thumbPainter
+  )
+
+  Column(
+    modifier = Modifier.padding(4.dp)
+  ) {
+    Box {
+      Image(
+        drinkPainter,
+        contentDescription = null,
+        contentScale = ContentScale.Fit,
+        modifier = Modifier
+          .clip(CircleShape)
+          .background(MaterialTheme.colorScheme.primaryContainer)
+          .aspectRatio(1.0f)
+          .padding(8.dp)
+      )
+      Text(
+        String.format("%.02f €", item.price),
+        color = MaterialTheme.colorScheme.onPrimary,
+        fontWeight = FontWeight.SemiBold,
+        modifier = Modifier
+          .padding(vertical = 12.dp)
+          .clip(RoundedCornerShape(16.dp))
+          .background(MaterialTheme.colorScheme.primary)
+          .align(Alignment.BottomEnd)
+          .padding(horizontal = 8.dp)
+      )
+    }
+    Text(
+      item.name,
+      fontWeight = FontWeight.SemiBold,
+      maxLines = 1,
+      overflow = TextOverflow.Ellipsis,
+      modifier = Modifier.padding(horizontal = 4.dp)
+    )
+    Row(
+      horizontalArrangement = Arrangement.SpaceBetween,
+      modifier = Modifier
+        .padding(horizontal = 4.dp)
+        .fillMaxWidth()
+    ) {
+      Text(
+        String.format("%.02f l", item.volume),
+        color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
+      )
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListScreen.kt
similarity index 52%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListScreen.kt
index 61340f69ba23abacd4d66ceb74da0fe94ff9d5df..9092436d6a46f478fcfcb032b1681c8dda884de6 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListScreen.kt
@@ -22,53 +22,53 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui
+package de.chaosdorf.meteroid.ui.money
 
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.NavigationBar
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
 import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
+import de.chaosdorf.meteroid.ui.navigation.HomeSections
+import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
+import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
 
 @Composable
-fun <T : MeteroidNavSection> MeteroidScaffold(
-  routes: Iterable<T>,
-  currentRoute: T,
-  navigateTo: (String) -> Unit,
-  onBack: () -> Unit,
-  content: @Composable (PaddingValues) -> Unit
+fun MoneyListScreen(
+  viewModel: MoneyListViewModel,
+  onNavigate: (String) -> Unit = {}
 ) {
+  val account by viewModel.account.collectAsState()
+
   Scaffold(
-    topBar = {
-      TopAppBar(
-        title = { Text("Meteroid") },
-        navigationIcon = {
-          IconButton(onClick = onBack) {
-            Icon(Icons.Default.Menu, contentDescription = "Menu")
-          }
-        },
-        modifier = Modifier
-          .padding(8.dp)
-          .shadow(4.dp, shape = RoundedCornerShape(8.dp))
-          .zIndex(1.0f)
-      )
-    },
+    topBar = { MeteroidTopBar(account, onNavigate) },
     bottomBar = {
-      NavigationBar {
-        MeteroidNavSections(routes, currentRoute, navigateTo)
+      MeteroidBottomBar(
+        currentRoute = HomeSections.DEPOSIT,
+        historyEnabled = account?.second?.audit == true,
+        navigateTo = onNavigate
+      )
+    }
+  ) { paddingValues: PaddingValues ->
+    Column {
+      LazyVerticalGrid(
+        GridCells.Adaptive(120.dp),
+        modifier = Modifier.padding(paddingValues),
+        contentPadding = PaddingValues(12.dp),
+        horizontalArrangement = Arrangement.SpaceBetween,
+      ) {
+        items(viewModel.money) { monetaryAmount ->
+          MoneyTile(monetaryAmount)
+        }
       }
-    },
-    content = content
-  )
+    }
+  }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..138661767ee0c3a97d721eec86371e82041258f7
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyListViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.money
+
+import androidx.annotation.DrawableRes
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.meteroid.R
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.User
+import de.chaosdorf.meteroid.sync.AccountProvider
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+enum class MonetaryAmount(val amount: Double, @DrawableRes val image: Int) {
+  MONEY_50(0.50, R.drawable.euro_50),
+  MONEY_100(1.00, R.drawable.euro_100),
+  MONEY_200(2.00, R.drawable.euro_200),
+  MONEY_500(5.00, R.drawable.euro_500),
+  MONEY_1000(10.00, R.drawable.euro_1000),
+  MONEY_2000(20.00, R.drawable.euro_2000),
+  MONEY_5000(50.00, R.drawable.euro_5000),
+}
+
+@HiltViewModel
+class MoneyListViewModel @Inject constructor(
+  accountProvider: AccountProvider
+) : ViewModel() {
+  val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
+    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+
+  val money: List<MonetaryAmount> = MonetaryAmount.entries
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyTile.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyTile.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f584cb0ebd1db92969539ae4c885d9e59ab154d3
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/money/MoneyTile.kt
@@ -0,0 +1,77 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.money
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun MoneyTile(
+  item: MonetaryAmount
+) {
+  Column(
+    modifier = Modifier.padding(4.dp)
+  ) {
+    Box {
+      Image(
+        painterResource(item.image),
+        contentDescription = null,
+        contentScale = ContentScale.Fit,
+        modifier = Modifier
+          .clip(CircleShape)
+          .background(MaterialTheme.colorScheme.primaryContainer)
+          .aspectRatio(1.0f)
+          .padding(8.dp)
+      )
+      Text(
+        String.format("%.02f €", item.amount),
+        color = MaterialTheme.colorScheme.onPrimary,
+        fontWeight = FontWeight.SemiBold,
+        modifier = Modifier
+          .padding(vertical = 12.dp)
+          .clip(RoundedCornerShape(16.dp))
+          .background(MaterialTheme.colorScheme.primary)
+          .align(Alignment.BottomEnd)
+          .padding(horizontal = 8.dp)
+      )
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/HomeSections.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/HomeSections.kt
similarity index 84%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/HomeSections.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/HomeSections.kt
index 80d9d5eab7542df27750aeac6e8d18d6f775e3eb..c0879743211d72fca287405f5dbd5243784856c1 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/HomeSections.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/HomeSections.kt
@@ -22,15 +22,14 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home
+package de.chaosdorf.meteroid.ui.navigation
 
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.twotone.Money
+import androidx.compose.material.icons.twotone.History
+import androidx.compose.material.icons.twotone.LocalAtm
 import androidx.compose.ui.graphics.vector.ImageVector
 import de.chaosdorf.meteroid.icons.MeteroidIcons
 import de.chaosdorf.meteroid.icons.twotone.WaterFull
-import de.chaosdorf.meteroid.ui.MeteroidNavSection
-import de.chaosdorf.meteroid.ui.Routes
 
 enum class HomeSections(
   override val title: String,
@@ -38,5 +37,6 @@ enum class HomeSections(
   override val route: String
 ) : MeteroidNavSection {
   PURCHASE("Drinks", MeteroidIcons.TwoTone.WaterFull, Routes.Home.Purchase),
-  DEPOSIT("Money", Icons.TwoTone.Money, Routes.Home.Deposit);
+  DEPOSIT("Money", Icons.TwoTone.LocalAtm, Routes.Home.Deposit),
+  HISTORY("History", Icons.TwoTone.History, Routes.Home.History);
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidNavSection.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidBottomBar.kt
similarity index 71%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidNavSection.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidBottomBar.kt
index 4a3a6092479780c5c0a0264dc877102e1841bf06..ebe94f2df394db701947871f6d81811a7ef25373 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidNavSection.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidBottomBar.kt
@@ -22,36 +22,32 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui
+package de.chaosdorf.meteroid.ui.navigation
 
-import androidx.compose.foundation.layout.RowScope
 import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
 import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-
-interface MeteroidNavSection {
-  val title: String
-  val icon: ImageVector
-  val route: String
-}
 
 @Composable
-fun <T : MeteroidNavSection> RowScope.MeteroidNavSections(
-  routes: Iterable<T>,
+fun <T : MeteroidNavSection> MeteroidBottomBar(
   currentRoute: T,
   navigateTo: (String) -> Unit,
+  historyEnabled: Boolean,
   modifier: Modifier = Modifier
 ) {
-  for (route in routes) {
-    NavigationBarItem(
-      icon = { Icon(route.icon, contentDescription = route.title) },
-      label = { Text(route.title) },
-      selected = route == currentRoute,
-      onClick = { navigateTo(route.route) },
-      modifier = modifier
-    )
+  NavigationBar {
+    for (route in HomeSections.entries) {
+      NavigationBarItem(
+        icon = { Icon(route.icon, contentDescription = route.title) },
+        label = { Text(route.title) },
+        selected = route == currentRoute,
+        onClick = { navigateTo(route.route) },
+        modifier = modifier,
+        enabled = route != HomeSections.HISTORY || historyEnabled
+      )
+    }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavSection.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavSection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f89144fcbf37f8c13f75dc011de5c305e8bf85f4
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavSection.kt
@@ -0,0 +1,33 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.navigation
+
+import androidx.compose.ui.graphics.vector.ImageVector
+
+interface MeteroidNavSection {
+  val title: String
+  val icon: ImageVector
+  val route: String
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
new file mode 100644
index 0000000000000000000000000000000000000000..798b8a854e1f3f726788cdf94414f39e830f2c81
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
@@ -0,0 +1,89 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.navigation
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.User
+
+@Composable
+fun MeteroidTopBar(
+  account: Pair<Server, User?>?,
+  onNavigate: (String) -> Unit = {}
+) {
+  TopAppBar(
+    title = {
+      Text(
+        account?.second?.name
+          ?: account?.first?.name
+          ?: "Meteroid"
+      )
+    },
+    navigationIcon = {
+      IconButton(onClick = { onNavigate(Routes.Users.Root) }) {
+        Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
+      }
+    },
+    actions = {
+      account?.second?.let { user ->
+        val (foreground, background) =
+          if (user.balance < 0)
+            Pair(MaterialTheme.colorScheme.onError, MaterialTheme.colorScheme.error)
+          else
+            Pair(MaterialTheme.colorScheme.onPrimary, MaterialTheme.colorScheme.primary)
+
+        Text(
+          String.format("%.02f €", user.balance),
+          color = foreground,
+          fontSize = 14.sp,
+          lineHeight = 20.sp,
+          fontWeight = FontWeight.SemiBold,
+          modifier = Modifier
+            .padding(end = 20.dp)
+            .clip(RoundedCornerShape(16.dp))
+            .background(background)
+            .padding(horizontal = 8.dp)
+        )
+      }
+    },
+    modifier = Modifier.shadow(4.dp)
+  )
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/Routes.kt
similarity index 85%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/Routes.kt
index e06f5c2fee3833b6118151c546cec5ca931cda54..f9938c462ca77b3fdf2ee291a48d3394cbeb68fe 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/Routes.kt
@@ -22,7 +22,7 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui
+package de.chaosdorf.meteroid.ui.navigation
 
 object Routes {
   const val Init = "init"
@@ -35,13 +35,14 @@ object Routes {
 
   object Users {
     const val Root = "users"
-    const val List = "${Root}/list"
-    //const val Add = "${Root}/new"
+    const val List = "$Root/list"
+    //const val Add = "$Root/new"
   }
 
   object Home {
     const val Root = "home"
-    const val Deposit = "home/deposit"
-    const val Purchase = "home/purchase"
+    const val Deposit = "$Root/deposit"
+    const val Purchase = "$Root/purchase"
+    const val History = "$Root/history"
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListItem.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ed56a75c8623e0e0dca5d6d33c6ef73bfe48d6b4
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListItem.kt
@@ -0,0 +1,139 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.purchases
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.AttachMoney
+import androidx.compose.material.icons.filled.QuestionMark
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.rememberAsyncImagePainter
+import de.chaosdorf.meteroid.model.Drink
+import de.chaosdorf.meteroid.model.Purchase
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toJavaLocalDateTime
+import kotlinx.datetime.toLocalDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+@Composable
+fun PurchaseListItem(
+  purchase: Purchase,
+  drink: Drink?
+) {
+  val timestamp = purchase.createdAt.toLocalDateTime(TimeZone.currentSystemDefault())
+  val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
+
+  ListItem(
+    headlineContent = {
+      val label =
+        if (drink != null) drink.name
+        else if (purchase.difference > 0.0) "Deposit"
+        else "Unknown"
+      Text(label)
+    },
+    supportingContent = {
+      Text(formatter.format(timestamp.toJavaLocalDateTime()))
+    },
+    leadingContent = {
+      Box(
+        modifier = Modifier
+          .size(48.dp)
+          .clip(CircleShape)
+          .background(MaterialTheme.colorScheme.primaryContainer)
+          .aspectRatio(1.0f)
+      ) {
+        if (drink != null) {
+          val thumbPainter = rememberAsyncImagePainter(
+            drink.logoUrl
+          )
+          val originalPainter = rememberAsyncImagePainter(
+            drink.logoUrl.replace("/thumb/", "/original/"),
+            error = thumbPainter
+          )
+
+          Image(
+            painter = originalPainter,
+            contentDescription = null,
+            contentScale = ContentScale.Fit,
+            modifier = Modifier
+              .align(Alignment.Center)
+              .fillMaxSize()
+          )
+        } else if (purchase.difference > 0) {
+          Icon(
+            Icons.Default.AttachMoney,
+            contentDescription = null,
+            modifier = Modifier.align(Alignment.Center)
+          )
+        } else {
+          Icon(
+            Icons.Default.QuestionMark,
+            contentDescription = null,
+            modifier = Modifier.align(Alignment.Center)
+          )
+        }
+      }
+    },
+    trailingContent = {
+      val (foreground, background) =
+        if (purchase.difference < 0)
+          Pair(MaterialTheme.colorScheme.onError, MaterialTheme.colorScheme.error)
+        else
+          Pair(MaterialTheme.colorScheme.onPrimary, MaterialTheme.colorScheme.primary)
+
+      Text(
+        String.format("%.02f €", purchase.difference),
+        color = foreground,
+        fontSize = 14.sp,
+        lineHeight = 20.sp,
+        fontWeight = FontWeight.SemiBold,
+        modifier = Modifier
+          .clip(RoundedCornerShape(16.dp))
+          .background(background)
+          .padding(horizontal = 8.dp)
+      )
+    }
+  )
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListScreen.kt
similarity index 57%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListScreen.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListScreen.kt
index f80918e98f9189613e6201189aff3fff46ceef3f..84d1f8b4e8953644635124db85d0bf6ae97598ba 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/DrinkListScreen.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseListScreen.kt
@@ -22,40 +22,51 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home
+package de.chaosdorf.meteroid.ui.purchases
 
-import android.annotation.SuppressLint
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.Text
+import androidx.compose.material3.Scaffold
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.lifecycle.viewmodel.compose.viewModel
-import de.chaosdorf.meteroid.storage.SyncHandler
+import de.chaosdorf.meteroid.sync.SyncHandler
+import de.chaosdorf.meteroid.ui.navigation.HomeSections
+import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
+import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
 
-@Preview
 @Composable
-fun DrinkListScreen(
-  viewModel: DrinkListViewModel = viewModel(),
-  @SuppressLint("ModifierParameter") modifier: Modifier = Modifier
+fun PurchaseListScreen(
+  viewModel: PurchaseViewModel,
+  onNavigate: (route: String) -> Unit = {}
 ) {
-  val drinks by viewModel.drinks.collectAsState()
+  val account by viewModel.account.collectAsState()
+  val purchases by viewModel.purchases.collectAsState()
   val syncState by viewModel.syncState.collectAsState()
 
-  Column(modifier = modifier) {
-    if (syncState == SyncHandler.State.Loading) {
-      LinearProgressIndicator()
+  Scaffold(
+    topBar = { MeteroidTopBar(account, onNavigate) },
+    bottomBar = {
+      MeteroidBottomBar(
+        currentRoute = HomeSections.HISTORY,
+        historyEnabled = account?.second?.audit == true,
+        navigateTo = onNavigate
+      )
     }
-    LazyColumn {
-      items(drinks) { drink ->
-        ListItem(headlineContent = { Text(drink.name) },
-          supportingContent = { Text("${drink.volume}l · ${drink.price}€") })
+  ) { paddingValues: PaddingValues ->
+    Column {
+      if (syncState == SyncHandler.State.Loading) {
+        LinearProgressIndicator()
+      }
+      LazyColumn(modifier = Modifier.padding(paddingValues)) {
+        items(purchases) { (purchase, drink) ->
+          PurchaseListItem(purchase, drink)
+        }
       }
     }
   }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..86a6af815d79682e971f83644f8f183cd1f250a1
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchases/PurchaseViewModel.kt
@@ -0,0 +1,104 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.ui.purchases
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.meteroid.model.Drink
+import de.chaosdorf.meteroid.model.DrinkRepository
+import de.chaosdorf.meteroid.model.Purchase
+import de.chaosdorf.meteroid.model.PurchaseRepository
+import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.User
+import de.chaosdorf.meteroid.sync.AccountProvider
+import de.chaosdorf.meteroid.sync.PurchaseSyncHandler
+import de.chaosdorf.meteroid.sync.SyncHandler
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.minutes
+
+@HiltViewModel
+class PurchaseViewModel @Inject constructor(
+  accountProvider: AccountProvider,
+  repository: PurchaseRepository,
+  drinkRepository: DrinkRepository,
+  syncHandler: PurchaseSyncHandler
+) : ViewModel() {
+  val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
+    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+
+  val purchases: StateFlow<List<Pair<Purchase, Drink?>>> = accountProvider.account
+    .flatMapLatest { account ->
+      account?.let { (server, user) ->
+        user?.let { user ->
+          combine(
+            repository.getAllFlow(server.serverId, user.userId),
+            drinkRepository.getAllFlow(server.serverId)
+          ) { purchases, drinks ->
+            purchases.map { purchase ->
+              Pair(purchase, drinks.firstOrNull { drink -> drink.drinkId == purchase.drinkId })
+            }
+          }
+        }
+      } ?: flowOf(emptyList())
+    }.mapLatest { list ->
+      list.mergeAdjecentDeposits()
+        .filter { it.second != null || it.first.difference != 0.0 }
+    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
+
+  val syncState: StateFlow<SyncHandler.State> = syncHandler.state
+}
+
+fun List<Pair<Purchase, Drink?>>.mergeAdjecentDeposits(): List<Pair<Purchase, Drink?>> {
+  val result = mutableListOf<Pair<Purchase, Drink?>>()
+  for (entry in this) {
+    val previous = result.lastOrNull()
+    if (previous != null
+      && previous.first.difference > 0
+      && entry.first.difference > 0
+      && previous.second == null
+      && entry.second == null
+      && entry.first.createdAt.minus(previous.first.createdAt) < 5.minutes
+    ) {
+      result.removeLast()
+      result.add(
+        Pair(
+          entry.first.copy(difference = entry.first.difference + previous.first.difference),
+          null
+        )
+      )
+    } else {
+      result.add(entry)
+    }
+  }
+  return result
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/ServerListScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/ServerListScreen.kt
index b30db84d789c8883babd31304c1ce541a5a1999e..ff0c99bb0f4780eef72be228c453163238036b59 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/ServerListScreen.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/ServerListScreen.kt
@@ -25,15 +25,21 @@
 package de.chaosdorf.meteroid.ui.servers
 
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.ListItem
+import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import de.chaosdorf.meteroid.model.ServerId
 
@@ -45,18 +51,31 @@ fun ServerListScreen(
   onSelect: (ServerId) -> Unit = {}
 ) {
   val servers by viewModel.servers.collectAsState()
-  LazyColumn {
-    items(servers) { server ->
-      ListItem(
-        headlineContent = { Text(server.name ?: server.url) },
-        modifier = Modifier.clickable { onSelect(server.serverId) }
+
+  Scaffold(
+    topBar = {
+      TopAppBar(
+        title = { Text("Meteroid") },
+        modifier = Modifier.shadow(4.dp)
       )
     }
-    item {
-      ListItem(
-        headlineContent = { Text("Add Server") },
-        modifier = Modifier.clickable { onAdd() }
-      )
+  ) { paddingValues ->
+    Column {
+      LazyColumn(modifier = Modifier.padding(paddingValues)) {
+        items(servers) { server ->
+          ListItem(
+            headlineContent = { Text(server.name ?: server.url) },
+            supportingContent = { if (server.name != null) Text(server.url) },
+            modifier = Modifier.clickable { onSelect(server.serverId) }
+          )
+        }
+        item {
+          ListItem(
+            headlineContent = { Text("Add Server") },
+            modifier = Modifier.clickable { onAdd() }
+          )
+        }
+      }
     }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Theme.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Theme.kt
index dbc712cf33722d47815b41e6d6941a80fedb26c8..db521b5df691e3ce5e5e4865cf185be016154730 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Theme.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Theme.kt
@@ -40,55 +40,55 @@ import androidx.compose.ui.platform.LocalView
 import androidx.core.view.WindowCompat
 
 private val DarkColorScheme = darkColorScheme(
-    primary = Purple80,
-    secondary = PurpleGrey80,
-    tertiary = Pink80
+  primary = Purple80,
+  secondary = PurpleGrey80,
+  tertiary = Pink80
 )
 
 private val LightColorScheme = lightColorScheme(
-    primary = Purple40,
-    secondary = PurpleGrey40,
-    tertiary = Pink40
+  primary = Purple40,
+  secondary = PurpleGrey40,
+  tertiary = Pink40
 
-    /* Other default colors to override
-    background = Color(0xFFFFFBFE),
-    surface = Color(0xFFFFFBFE),
-    onPrimary = Color.White,
-    onSecondary = Color.White,
-    onTertiary = Color.White,
-    onBackground = Color(0xFF1C1B1F),
-    onSurface = Color(0xFF1C1B1F),
-    */
+  /* Other default colors to override
+  background = Color(0xFFFFFBFE),
+  surface = Color(0xFFFFFBFE),
+  onPrimary = Color.White,
+  onSecondary = Color.White,
+  onTertiary = Color.White,
+  onBackground = Color(0xFF1C1B1F),
+  onSurface = Color(0xFF1C1B1F),
+  */
 )
 
 @Composable
 fun MeteroidTheme(
-    darkTheme: Boolean = isSystemInDarkTheme(),
-    // Dynamic color is available on Android 12+
-    dynamicColor: Boolean = true,
-    content: @Composable () -> Unit
+  darkTheme: Boolean = isSystemInDarkTheme(),
+  // Dynamic color is available on Android 12+
+  dynamicColor: Boolean = true,
+  content: @Composable () -> Unit
 ) {
-    val colorScheme = when {
-        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
-            val context = LocalContext.current
-            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
-        }
-
-        darkTheme -> DarkColorScheme
-        else -> LightColorScheme
+  val colorScheme = when {
+    dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+      val context = LocalContext.current
+      if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
     }
-    val view = LocalView.current
-    if (!view.isInEditMode) {
-        SideEffect {
-            val window = (view.context as Activity).window
-            window.statusBarColor = colorScheme.primary.toArgb()
-            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
-        }
+
+    darkTheme -> DarkColorScheme
+    else -> LightColorScheme
+  }
+  val view = LocalView.current
+  if (!view.isInEditMode) {
+    SideEffect {
+      val window = (view.context as Activity).window
+      window.statusBarColor = colorScheme.primary.toArgb()
+      WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
     }
+  }
 
-    MaterialTheme(
-        colorScheme = colorScheme,
-        typography = Typography,
-        content = content
-    )
+  MaterialTheme(
+    colorScheme = colorScheme,
+    typography = Typography,
+    content = content
+  )
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Type.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Type.kt
index 31e4b39550b391efd9b29299c351e1bec7f83cf8..e6759ded7701183e45a771ab7713993b1a15df77 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Type.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/theme/Type.kt
@@ -8,27 +8,27 @@ import androidx.compose.ui.unit.sp
 
 // Set of Material typography styles to start with
 val Typography = Typography(
-    bodyLarge = TextStyle(
-        fontFamily = FontFamily.Default,
-        fontWeight = FontWeight.Normal,
-        fontSize = 16.sp,
-        lineHeight = 24.sp,
-        letterSpacing = 0.5.sp
-    )
-    /* Other default text styles to override
-    titleLarge = TextStyle(
-        fontFamily = FontFamily.Default,
-        fontWeight = FontWeight.Normal,
-        fontSize = 22.sp,
-        lineHeight = 28.sp,
-        letterSpacing = 0.sp
-    ),
-    labelSmall = TextStyle(
-        fontFamily = FontFamily.Default,
-        fontWeight = FontWeight.Medium,
-        fontSize = 11.sp,
-        lineHeight = 16.sp,
-        letterSpacing = 0.5.sp
-    )
-    */
-)
\ No newline at end of file
+  bodyLarge = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 16.sp,
+    lineHeight = 24.sp,
+    letterSpacing = 0.5.sp
+  )
+  /* Other default text styles to override
+  titleLarge = TextStyle(
+      fontFamily = FontFamily.Default,
+      fontWeight = FontWeight.Normal,
+      fontSize = 22.sp,
+      lineHeight = 28.sp,
+      letterSpacing = 0.sp
+  ),
+  labelSmall = TextStyle(
+      fontFamily = FontFamily.Default,
+      fontWeight = FontWeight.Medium,
+      fontSize = 11.sp,
+      lineHeight = 16.sp,
+      letterSpacing = 0.5.sp
+  )
+  */
+)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListScreen.kt
index 678516dec2500c912ede880d8ac1dfcba61b0282..92cbdad4aea35a18fd0b5292e6b3430b5031767f 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListScreen.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListScreen.kt
@@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ArrowBack
 import androidx.compose.material3.Icon
@@ -44,48 +43,48 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
-import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
-import androidx.lifecycle.viewmodel.compose.viewModel
 import de.chaosdorf.mete.UserId
-import de.chaosdorf.meteroid.storage.SyncHandler
+import de.chaosdorf.meteroid.sync.SyncHandler
 
-@Preview
 @Composable
 fun UserListScreen(
-  viewModel: UserListViewModel = viewModel(),
+  viewModel: UserListViewModel,
   onAdd: () -> Unit = {},
   onSelect: (UserId) -> Unit = {},
   onBack: () -> Unit = {},
 ) {
+  val server by viewModel.account.collectAsState()
   val users by viewModel.users.collectAsState()
   val syncState by viewModel.syncState.collectAsState()
 
   Scaffold(
     topBar = {
       TopAppBar(
-        title = { Text("Meteroid") },
+        title = {
+          Text(
+            server?.first?.name
+              ?: "Meteroid"
+          )
+        },
         navigationIcon = {
-          IconButton(onClick = onBack) {
+          IconButton(onClick = { onBack() }) {
             Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
           }
         },
-        modifier = Modifier
-          .padding(8.dp)
-          .shadow(4.dp, shape = RoundedCornerShape(8.dp))
-          .zIndex(1.0f)
+        modifier = Modifier.shadow(4.dp)
       )
     }
   ) { paddingValues ->
-    Column(modifier = Modifier.padding(paddingValues)) {
+    Column {
       if (syncState == SyncHandler.State.Loading) {
         LinearProgressIndicator()
       }
-      LazyColumn {
+      LazyColumn(modifier = Modifier.padding(paddingValues)) {
         items(users) { user ->
           ListItem(
             headlineContent = { Text(user.name) },
+            supportingContent = { Text(user.email) },
             modifier = Modifier.clickable { onSelect(user.userId) }
           )
         }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListViewModel.kt
index ef0f243759c6d82ad6d343e62c41a33bc4d0b4e8..7e74e6bb4c2625f722938f8bb974696b5749017f 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/users/UserListViewModel.kt
@@ -27,30 +27,34 @@ package de.chaosdorf.meteroid.ui.users
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.User
 import de.chaosdorf.meteroid.model.UserRepository
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.storage.UserSyncHandler
+import de.chaosdorf.meteroid.sync.AccountProvider
+import de.chaosdorf.meteroid.sync.SyncHandler
+import de.chaosdorf.meteroid.sync.UserSyncHandler
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import javax.inject.Inject
 
 @HiltViewModel
 class UserListViewModel @Inject constructor(
-  accountPreferences: AccountPreferences,
-  userRepository: UserRepository,
+  accountProvider: AccountProvider,
+  repository: UserRepository,
   syncHandler: UserSyncHandler
 ) : ViewModel() {
-  private val serverId = accountPreferences.state.mapLatest { it.server }
+  val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
+    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
 
-  val users: StateFlow<List<User>> = serverId.flatMapLatest { serverId ->
-    if (serverId == null) flowOf(emptyList())
-    else userRepository.getAllFlow(serverId)
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
+  val users: StateFlow<List<User>> = accountProvider.account
+    .flatMapLatest { account ->
+      account?.let { (server, _) ->
+        repository.getAllFlow(server.serverId)
+      } ?: flowOf(emptyList())
+    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
 
-  val syncState = syncHandler.state
+  val syncState: StateFlow<SyncHandler.State> = syncHandler.state
 }
diff --git a/app/src/main/res/drawable-nodpi/euro_100.png b/app/src/main/res/drawable-nodpi/euro_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..c25bfa9937bf5967d34ddeba149a8dd403fb38f5
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_100.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_1000.png b/app/src/main/res/drawable-nodpi/euro_1000.png
new file mode 100644
index 0000000000000000000000000000000000000000..a9100393f67d7f42e2ce25df7e276ee8c756da36
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_1000.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_200.png b/app/src/main/res/drawable-nodpi/euro_200.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e9f69d9220105c731134504675ee021d1e29e6f
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_200.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_2000.png b/app/src/main/res/drawable-nodpi/euro_2000.png
new file mode 100644
index 0000000000000000000000000000000000000000..8a73934afb6e7ea5533dad7aacb0b4f4e26fc32c
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_2000.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_50.png b/app/src/main/res/drawable-nodpi/euro_50.png
new file mode 100644
index 0000000000000000000000000000000000000000..0874c2d70abc2d57314a351ad9f6092e0b00805a
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_50.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_500.png b/app/src/main/res/drawable-nodpi/euro_500.png
new file mode 100644
index 0000000000000000000000000000000000000000..541261000c834577f31a610e4a9d1059b74205b9
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_500.png differ
diff --git a/app/src/main/res/drawable-nodpi/euro_5000.png b/app/src/main/res/drawable-nodpi/euro_5000.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e8387e6ecc49f6da878c071f2ed79b9726f5f08
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/euro_5000.png differ
diff --git a/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
index 629dd50abcdc62ac44805f8b32c58f58cf89e311..a6a58c475dcc2b736963bf649aad2bbd940a4f26 100644
--- a/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
+++ b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
@@ -5,7 +5,7 @@ import org.gradle.api.Project
 import org.gradle.kotlin.dsl.configure
 import util.cmd
 import util.properties
-import java.util.*
+import java.util.Locale
 
 class AndroidApplicationConvention : Plugin<Project> {
   override fun apply(target: Project) {
@@ -48,6 +48,8 @@ class AndroidApplicationConvention : Plugin<Project> {
         compileOptions {
           sourceCompatibility = JavaVersion.VERSION_17
           targetCompatibility = JavaVersion.VERSION_17
+
+          isCoreLibraryDesugaringEnabled = true
         }
 
         testOptions {
diff --git a/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
index c048de7841d78191de848ec5808a009d99f0fa52..ead4e60c0cec6ebd386f3b559a5210b89e8a51d2 100644
--- a/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
+++ b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
@@ -2,7 +2,6 @@ import com.android.build.api.dsl.LibraryExtension
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.kotlin.dsl.configure
-import java.util.*
 
 class AndroidLibraryConvention : Plugin<Project> {
   override fun apply(target: Project) {
@@ -24,6 +23,10 @@ class AndroidLibraryConvention : Plugin<Project> {
           testInstrumentationRunnerArguments["disableAnalytics"] = "true"
         }
 
+        compileOptions {
+          isCoreLibraryDesugaringEnabled = true
+        }
+
         lint {
           warningsAsErrors = true
           lintConfig = file("../lint.xml")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6d134b53a8a807526f367db7ac77de8022a63134..0e5d0c5d8b42f548935dd864d8295394a0983cb0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,11 +8,13 @@ androidx-compose-material = "1.5.0-alpha04"
 androidx-compose-material3 = "1.2.0-alpha10"
 androidx-compose-runtimetracing = "1.0.0-alpha04"
 androidx-compose-tooling = "1.6.0-alpha08"
+androidx-datastore = "1.0.0"
 androidx-hilt = "1.0.0"
-androidx-navigation = "2.7.4"
+androidx-navigation = "2.7.5"
 androidx-room = "2.6.0"
 coil = "2.4.0"
 dagger-hilt = "2.48.1"
+desugar-jdk = "2.0.4"
 kotlin = "1.9.10"
 kotlin-ksp = "1.9.10-1.0.13"
 kotlinxCoroutines = "1.7.1"
@@ -44,6 +46,8 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin
 androidx-compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidx-compose-tooling" }
 androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
 
+androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
+
 androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
 androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
 androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
@@ -72,6 +76,8 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
 kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
 kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
 
+desugar-jdk = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-jdk" }
+
 # Dependencies of the included build-logic
 android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
 kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
diff --git a/persistence/build.gradle.kts b/persistence/build.gradle.kts
index fe766b996906bd3699652432eaea9ca24a80c6ea..0f261366d71c6daad151e60fff55b86edb47657c 100644
--- a/persistence/build.gradle.kts
+++ b/persistence/build.gradle.kts
@@ -37,6 +37,9 @@ ksp {
 
 dependencies {
   implementation(libs.kotlin.stdlib)
+  implementation(libs.kotlinx.datetime)
+  implementation(libs.kotlinx.serialization.json)
+  coreLibraryDesugaring(libs.desugar.jdk)
 
   implementation(libs.kotlinx.coroutines.core)
   testImplementation(libs.kotlinx.coroutines.test)
@@ -54,7 +57,5 @@ dependencies {
   implementation(libs.hilt.android)
   ksp(libs.hilt.compiler)
 
-  implementation(libs.kotlinx.datetime)
-  implementation(libs.kotlinx.serialization.json)
   implementation(project(":api"))
 }
diff --git a/persistence/room/schemas/de.chaosdorf.meteroid.MeteroidDatabase/1.json b/persistence/room/schemas/de.chaosdorf.meteroid.MeteroidDatabase/1.json
index f78e5632816d67577e2735d078c9603ac4814075..b8a0cbb9f2075143fb44088bef045f59af22274f 100644
--- a/persistence/room/schemas/de.chaosdorf.meteroid.MeteroidDatabase/1.json
+++ b/persistence/room/schemas/de.chaosdorf.meteroid.MeteroidDatabase/1.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 1,
-    "identityHash": "bee2a6e9f2b7a72c80a0e97b71f55e51",
+    "identityHash": "957bb839c1c422a256c594595437ee45",
     "entities": [
       {
         "tableName": "Drink",
@@ -239,12 +239,122 @@
             ]
           }
         ]
+      },
+      {
+        "tableName": "Purchase",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` INTEGER NOT NULL, `purchaseId` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `drinkId` INTEGER, `difference` REAL NOT NULL, `createdAt` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `purchaseId`), FOREIGN KEY(`serverId`) REFERENCES `Server`(`serverId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serverId`, `userId`) REFERENCES `User`(`serverId`, `userId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serverId`, `drinkId`) REFERENCES `Drink`(`serverId`, `drinkId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+        "fields": [
+          {
+            "fieldPath": "serverId",
+            "columnName": "serverId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "purchaseId",
+            "columnName": "purchaseId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userId",
+            "columnName": "userId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "drinkId",
+            "columnName": "drinkId",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "difference",
+            "columnName": "difference",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "createdAt",
+            "columnName": "createdAt",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "serverId",
+            "purchaseId"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Purchase_serverId_userId",
+            "unique": false,
+            "columnNames": [
+              "serverId",
+              "userId"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_Purchase_serverId_userId` ON `${TABLE_NAME}` (`serverId`, `userId`)"
+          },
+          {
+            "name": "index_Purchase_serverId_drinkId",
+            "unique": false,
+            "columnNames": [
+              "serverId",
+              "drinkId"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_Purchase_serverId_drinkId` ON `${TABLE_NAME}` (`serverId`, `drinkId`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Server",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "serverId"
+            ],
+            "referencedColumns": [
+              "serverId"
+            ]
+          },
+          {
+            "table": "User",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "serverId",
+              "userId"
+            ],
+            "referencedColumns": [
+              "serverId",
+              "userId"
+            ]
+          },
+          {
+            "table": "Drink",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "serverId",
+              "drinkId"
+            ],
+            "referencedColumns": [
+              "serverId",
+              "drinkId"
+            ]
+          }
+        ]
       }
     ],
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bee2a6e9f2b7a72c80a0e97b71f55e51')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '957bb839c1c422a256c594595437ee45')"
     ]
   }
 }
\ No newline at end of file
diff --git a/persistence/src/main/kotlin/de/chaosdorf/meteroid/MeteroidDatabase.kt b/persistence/src/main/kotlin/de/chaosdorf/meteroid/MeteroidDatabase.kt
index d360cc3e95c961de17e1a88bab8333f7e3ab03e8..a8b32864c10b8d73627ebc960cbe1809b224a17e 100644
--- a/persistence/src/main/kotlin/de/chaosdorf/meteroid/MeteroidDatabase.kt
+++ b/persistence/src/main/kotlin/de/chaosdorf/meteroid/MeteroidDatabase.kt
@@ -24,12 +24,13 @@
 
 package de.chaosdorf.meteroid
 
-import androidx.room.AutoMigration
 import androidx.room.Database
 import androidx.room.RoomDatabase
 import androidx.room.TypeConverters
 import de.chaosdorf.meteroid.model.Drink
 import de.chaosdorf.meteroid.model.DrinkRepository
+import de.chaosdorf.meteroid.model.Purchase
+import de.chaosdorf.meteroid.model.PurchaseRepository
 import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerRepository
 import de.chaosdorf.meteroid.model.User
@@ -41,7 +42,8 @@ import de.chaosdorf.meteroid.util.KotlinDatetimeTypeConverter
   entities = [
     Drink::class,
     Server::class,
-    User::class
+    User::class,
+    Purchase::class
   ],
   autoMigrations = [],
 )
@@ -50,4 +52,5 @@ abstract class MeteroidDatabase : RoomDatabase() {
   abstract fun drinks(): DrinkRepository
   abstract fun server(): ServerRepository
   abstract fun users(): UserRepository
+  abstract fun purchases(): PurchaseRepository
 }
diff --git a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Drink.kt b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Drink.kt
index c9435469aa0b97bc42f38bd48dd602f4c4301bd5..ade8587945246de5bcbb0d1adf2a93e3933e6cc2 100644
--- a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Drink.kt
+++ b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Drink.kt
@@ -34,6 +34,7 @@ import de.chaosdorf.mete.DrinkId
 import de.chaosdorf.mete.v1.DrinkModelV1
 import kotlinx.coroutines.flow.Flow
 import kotlinx.datetime.Instant
+import java.net.URI
 
 @Entity(
   primaryKeys = ["serverId", "drinkId"],
@@ -58,8 +59,8 @@ data class Drink(
   val logoUpdatedAt: Instant
 ) {
   companion object {
-    fun fromModelV1(serverId: ServerId, value: DrinkModelV1) = Drink(
-      serverId,
+    fun fromModelV1(server: Server, value: DrinkModelV1) = Drink(
+      server.serverId,
       value.id,
       value.active,
       value.name,
@@ -68,7 +69,7 @@ data class Drink(
       value.price,
       value.createdAt,
       value.updatedAt,
-      value.logoUrl,
+      URI.create(server.url).resolve(value.logoUrl).toString(),
       value.logoFileName,
       value.logoContentType,
       value.logoFileSize,
@@ -85,10 +86,10 @@ interface DrinkRepository {
   @Query("SELECT * FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId LIMIT 1")
   fun getFlow(serverId: ServerId, drinkId: DrinkId): Flow<Drink?>
 
-  @Query("SELECT * FROM Drink WHERE serverId = :serverId")
+  @Query("SELECT * FROM Drink WHERE serverId = :serverId ORDER BY NAME ASC")
   suspend fun getAll(serverId: ServerId): List<Drink>
 
-  @Query("SELECT * FROM Drink WHERE serverId = :serverId")
+  @Query("SELECT * FROM Drink WHERE serverId = :serverId ORDER BY NAME ASC")
   fun getAllFlow(serverId: ServerId): Flow<List<Drink>>
 
   @Insert(onConflict = OnConflictStrategy.REPLACE)
diff --git a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Purchase.kt b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Purchase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fad3d86e350b4c7afd02239cdb84c17a82577a9f
--- /dev/null
+++ b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Purchase.kt
@@ -0,0 +1,92 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package de.chaosdorf.meteroid.model
+
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import de.chaosdorf.mete.AuditEntryId
+import de.chaosdorf.mete.DrinkId
+import de.chaosdorf.mete.UserId
+import de.chaosdorf.mete.v1.AuditEntryModelV1
+import kotlinx.coroutines.flow.Flow
+import kotlinx.datetime.Instant
+
+@Entity(
+  primaryKeys = ["serverId", "purchaseId"],
+  foreignKeys = [
+    ForeignKey(Server::class, ["serverId"], ["serverId"], onDelete = ForeignKey.CASCADE),
+    ForeignKey(User::class, ["serverId", "userId"], ["serverId", "userId"], onDelete = ForeignKey.CASCADE),
+    ForeignKey(Drink::class, ["serverId", "drinkId"], ["serverId", "drinkId"], onDelete = ForeignKey.NO_ACTION)
+  ],
+  indices = [
+    Index("serverId", "userId"),
+    Index("serverId", "drinkId")
+  ]
+)
+data class Purchase(
+  val serverId: ServerId,
+  val purchaseId: AuditEntryId,
+  val userId: UserId,
+  val drinkId: DrinkId?,
+  val difference: Double,
+  val createdAt: Instant
+) {
+  companion object {
+    fun fromModelV1(server: Server, userId: UserId, value: AuditEntryModelV1) = Purchase(
+      server.serverId,
+      value.id,
+      userId,
+      value.drink,
+      value.difference,
+      value.createdAt
+    )
+  }
+}
+
+@Dao
+interface PurchaseRepository {
+  @Query("SELECT * FROM Purchase WHERE serverId = :serverId AND userId = :userId ORDER BY createdAt DESC")
+  suspend fun getAll(serverId: ServerId, userId: UserId): List<Purchase>
+
+  @Query("SELECT * FROM Purchase WHERE serverId = :serverId AND userId = :userId ORDER BY createdAt DESC")
+  fun getAllFlow(serverId: ServerId, userId: UserId): Flow<List<Purchase>>
+
+  @Insert(onConflict = OnConflictStrategy.REPLACE)
+  suspend fun save(purchase: Purchase)
+
+  @Query("DELETE FROM Purchase WHERE serverId = :serverId AND purchaseId = :purchaseId")
+  suspend fun delete(serverId: ServerId, purchaseId: AuditEntryId)
+
+  @Query("DELETE FROM Purchase WHERE serverId = :serverId AND userId = :userId")
+  suspend fun deleteAll(serverId: ServerId, userId: UserId)
+
+  @Query("DELETE FROM Purchase WHERE serverId = :serverId")
+  suspend fun deleteAll(serverId: ServerId)
+}
diff --git a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/User.kt b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/User.kt
index 1c669a62ee67b9ad8fb508d8a8b94b3775d2e648..5e1b16671c9786bb004e5e23e308c457ceea8f92 100644
--- a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/User.kt
+++ b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/User.kt
@@ -54,8 +54,8 @@ data class User(
   val updatedAt: Instant,
 ) {
   companion object {
-    fun fromModelV1(serverId: ServerId, value: UserModelV1) = User(
-      serverId,
+    fun fromModelV1(server: Server, value: UserModelV1) = User(
+      server.serverId,
       value.id,
       value.active,
       value.name,
@@ -77,10 +77,10 @@ interface UserRepository {
   @Query("SELECT * FROM User WHERE serverId = :serverId AND userId = :userId LIMIT 1")
   fun getFlow(serverId: ServerId, userId: UserId): Flow<User?>
 
-  @Query("SELECT * FROM User WHERE serverId = :serverId")
+  @Query("SELECT * FROM User WHERE serverId = :serverId ORDER BY NAME ASC")
   suspend fun getAll(serverId: ServerId): List<User>
 
-  @Query("SELECT * FROM User WHERE serverId = :serverId")
+  @Query("SELECT * FROM User WHERE serverId = :serverId ORDER BY NAME ASC")
   fun getAllFlow(serverId: ServerId): Flow<List<User>>
 
   @Insert(onConflict = OnConflictStrategy.REPLACE)