diff --git a/api/src/main/kotlin/de/chaosdorf/mete/model/ServerId.kt b/api/src/main/kotlin/de/chaosdorf/mete/model/ServerId.kt
index 39880c07be311f29bcdf603b0e67c84b311cbed0..ef0339084614aa6da562ae354384c455df6ce624 100644
--- a/api/src/main/kotlin/de/chaosdorf/mete/model/ServerId.kt
+++ b/api/src/main/kotlin/de/chaosdorf/mete/model/ServerId.kt
@@ -24,6 +24,9 @@
 
 package de.chaosdorf.mete.model
 
+import kotlinx.serialization.Serializable
+
+@Serializable
 @JvmInline
 value class ServerId(val value: Long) {
   override fun toString() = value.toString()
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 86f5a5a98dcb9140ee0e94ecaabfde4ecea18809..fc70942eb38278e03007515aaf21a89d517e1593 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -88,7 +88,9 @@ dependencies {
   implementation(libs.androidx.room.ktx)
   implementation(libs.androidx.room.paging)
 
-  implementation(libs.androidx.navigation.compose)
+  implementation(libs.androidx.navigation3.runtime)
+  implementation(libs.androidx.navigation3.ui)
+  implementation(libs.androidx.navigation3.viewmodel)
 
   implementation(libs.okhttp)
   implementation(libs.coil.compose)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt
index 88155e5d01941e26ede102391c8aec6a0ce60acc..c20fdae3230763e6db42afd74718e50b99201055 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt
@@ -27,33 +27,168 @@ package de.chaosdorf.meteroid
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.material3.Scaffold
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.get
+import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
+import androidx.navigation3.runtime.*
+import androidx.navigation3.ui.NavDisplay
+import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
 import dagger.hilt.android.AndroidEntryPoint
+import dagger.hilt.android.lifecycle.withCreationCallback
 import de.chaosdorf.meteroid.theme.MeteroidTheme
-import de.chaosdorf.meteroid.ui.MeteroidRouter
-
+import de.chaosdorf.meteroid.ui.*
+import de.chaosdorf.meteroid.viewmodel.*
 
 @AndroidEntryPoint
 class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
-    val viewModelProvider = ViewModelProvider(this)
-    val viewModel = viewModelProvider.get<MeteroidViewModel>()
+
+    val viewModel by viewModels<InitViewModel>(
+      extrasProducer = {
+        defaultViewModelCreationExtras.withCreationCallback<InitViewModelFactory> { factory ->
+          factory.create()
+        }
+      }
+    )
 
     installSplashScreen().setKeepOnScreenCondition {
-      viewModel.initialAccount.value == null
+      viewModel.setupComplete.value == null
     }
 
     setContent {
-      val initialAccount by viewModel.initialAccount.collectAsState()
+      val setupComplete by viewModel.setupComplete.collectAsState()
 
       MeteroidTheme {
-        if (initialAccount != null) {
-          MeteroidRouter(initialAccount!!)
+        if (setupComplete != null) {
+          val backStack: NavBackStack = rememberNavBackStack(
+            if (setupComplete == true) Routes.ServerList else Routes.Setup
+          )
+
+          Scaffold(
+            topBar = { TopBar(backStack) },
+            bottomBar = { BottomBar(backStack) }
+          ) { paddingValues ->
+            NavDisplay(
+              backStack = backStack,
+              onBack = { backStack.removeLastOrNull() },
+              entryDecorators = listOf(
+                rememberSceneSetupNavEntryDecorator(),
+                rememberSavedStateNavEntryDecorator(),
+                rememberViewModelStoreNavEntryDecorator(),
+              ),
+              // BEGIN WORKAROUND
+              // TODO FIXED IN navigation3 1.0.0-alpha04 Jun 18
+              transitionSpec = {
+                ContentTransform(
+                  fadeIn(animationSpec = snap()),
+                  fadeOut(animationSpec = snap())
+                )
+              },
+              popTransitionSpec = {
+                ContentTransform(
+                  fadeIn(animationSpec = snap()),
+                  fadeOut(animationSpec = snap())
+                )
+              },
+              predictivePopTransitionSpec = {
+                ContentTransform(
+                  fadeIn(animationSpec = snap()),
+                  fadeOut(animationSpec = snap())
+                )
+              },
+              // END
+              entryProvider = entryProvider {
+                entry<Routes.Setup> {
+                  val viewModel by viewModels<SetupViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<SetupViewModelFactory> { factory ->
+                        factory.create()
+                      }
+                    }
+                  )
+                  SetupRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.Settings> {
+                  val viewModel by viewModels<SettingsViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<SettingsViewModelFactory> { factory ->
+                        factory.create()
+                      }
+                    }
+                  )
+                  SettingsRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.ServerList> {
+                  val viewModel by viewModels<ServerListViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<ServerListViewModelFactory> { factory ->
+                        factory.create()
+                      }
+                    }
+                  )
+                  ServerListRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.UserList> {
+                  val viewModel by viewModels<UserListViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<UserListViewModelFactory> { factory ->
+                        factory.create(it.serverId.value)
+                      }
+                    }
+                  )
+                  UserListRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.Purchase> {
+                  val viewModel by viewModels<UserViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<UserViewModelFactory> { factory ->
+                        factory.create(it.serverId.value, it.userId.value)
+                      }
+                    }
+                  )
+                  PurchaseRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.Deposit> {
+                  val viewModel by viewModels<UserViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<UserViewModelFactory> { factory ->
+                        factory.create(it.serverId.value, it.userId.value)
+                      }
+                    }
+                  )
+                  DepositRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.History> {
+                  val viewModel by viewModels<UserViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<UserViewModelFactory> { factory ->
+                        factory.create(it.serverId.value, it.userId.value)
+                      }
+                    }
+                  )
+                  HistoryRoute(viewModel, backStack, paddingValues)
+                }
+                entry<Routes.Wrapped> {
+                  val viewModel by viewModels<WrappedViewModel>(
+                    extrasProducer = {
+                      defaultViewModelCreationExtras.withCreationCallback<WrappedViewModelFactory> { factory ->
+                        factory.create(it.serverId.value, it.userId.value)
+                      }
+                    }
+                  )
+                  WrappedRoute(viewModel, backStack, paddingValues)
+                }
+              }
+            )
+          }
         }
       }
     }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/AccountPreferencesImpl.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/storage/AccountPreferencesImpl.kt
index f74c0256e259f76f2cc20fe5720a5016eda0510c..01a814eaed2144c8afd7b8e58b64aead528d7d7f 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/storage/AccountPreferencesImpl.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/storage/AccountPreferencesImpl.kt
@@ -31,10 +31,12 @@ import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.longPreferencesKey
 import de.chaosdorf.mete.model.ServerId
 import de.chaosdorf.mete.model.UserId
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.mapLatest
 import javax.inject.Inject
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class AccountPreferencesImpl @Inject constructor(
   private val dataStore: DataStore<Preferences>
 ) : AccountPreferences {
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/theme/Theme.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/theme/Theme.kt
index 83927e00dc2967ad30c1e1c454424d9b85e129ed..83844ea67d6d91fc4f791679c6cdb92a02f0955c 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/theme/Theme.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/theme/Theme.kt
@@ -30,10 +30,8 @@ import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.*
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.unit.dp
 import androidx.core.view.WindowCompat
 
 private val LightColors = lightColorScheme(
@@ -104,7 +102,6 @@ private val DarkColors = darkColorScheme(
 @Composable
 fun MeteroidTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
-  // Dynamic color is available on Android 12+
   dynamicColor: Boolean = false,
   content: @Composable () -> Unit
 ) {
@@ -121,12 +118,8 @@ fun MeteroidTheme(
   if (!view.isInEditMode) {
     SideEffect {
       val window = (view.context as Activity).window
-      window.statusBarColor = colorScheme.primary.toArgb()
-      window.navigationBarColor = colorScheme.surfaceColorAtElevation(3.dp).toArgb()
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-        window.navigationBarDividerColor = colorScheme.surfaceColorAtElevation(3.dp).toArgb()
-      }
-      WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+      WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
+      WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !darkTheme
     }
   }
 
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Avatar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Avatar.kt
deleted file mode 100644
index c0861ac5f0dbe822352f32777f47e06bb8b7990f..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Avatar.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.*
-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.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import coil3.compose.AsyncImage
-import de.chaosdorf.meteroid.theme.icons.MeteroidIcons
-import de.chaosdorf.meteroid.theme.icons.filled.WaterFull
-
-@Preview
-@Composable
-fun UserAvatar(
-  source: String? = null,
-) {
-  var success by remember { mutableStateOf(false) }
-
-  AvatarLayout(
-    Modifier.clip(CircleShape)
-      .background(MaterialTheme.colorScheme.primaryContainer)
-  ) {
-    if (!success) {
-      Icon(
-        Icons.Filled.Person,
-        contentDescription = null,
-        tint = MaterialTheme.colorScheme.primary
-      )
-    }
-    AsyncImage(
-      source,
-      contentDescription = null,
-      contentScale = ContentScale.Crop,
-      modifier = Modifier.fillMaxSize(),
-      onSuccess = { success = true },
-      onError = { success = false },
-      onLoading = { success = false },
-    )
-  }
-}
-
-@Preview
-@Composable
-fun ServerAvatar(
-  source: String? = null,
-) {
-  var success by remember { mutableStateOf(false) }
-
-  AvatarLayout {
-    if (!success) {
-      Icon(
-        MeteroidIcons.Filled.WaterFull,
-        contentDescription = null
-      )
-    }
-    AsyncImage(
-      source,
-      contentDescription = null,
-      contentScale = ContentScale.Crop,
-      modifier = Modifier.fillMaxSize(),
-      onSuccess = { success = true },
-      onError = { success = false },
-      onLoading = { success = false },
-    )
-  }
-}
-
-@Composable
-fun AvatarLayout(
-  modifier: Modifier = Modifier,
-  content: @Composable () -> Unit
-) {
-  Box(
-    modifier.size(36.dp),
-    contentAlignment = Alignment.Center
-  ) {
-    content()
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/BottomBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/BottomBar.kt
new file mode 100644
index 0000000000000000000000000000000000000000..88db1368a9ab9cfb9f844c8682c1c7d9debfe608
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/BottomBar.kt
@@ -0,0 +1,112 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Celebration
+import androidx.compose.material.icons.outlined.History
+import androidx.compose.material.icons.outlined.LocalAtm
+import androidx.compose.material3.BottomAppBar
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.runtime.Composable
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.theme.icons.MeteroidIcons
+import de.chaosdorf.meteroid.theme.icons.outlined.WaterFull
+
+@Composable
+fun BottomBar(
+  backStack: NavBackStack,
+) {
+  val currentRoute = backStack.lastOrNull()
+
+  AnimatedVisibility(
+    visible = currentRoute is Routes.UserScope,
+    enter = slideInVertically(initialOffsetY = { it }),
+    exit = slideOutVertically(targetOffsetY = { it })
+  ) {
+    BottomAppBar {
+      NavigationBarItem(
+        selected = currentRoute is Routes.Purchase,
+        onClick = {
+          if (currentRoute is Routes.UserScope) {
+            backStack.remove(currentRoute)
+            backStack.add(
+              Routes.Purchase(currentRoute.serverId, currentRoute.userId)
+            )
+          }
+        },
+        icon = {
+          Icon(MeteroidIcons.Outlined.WaterFull, contentDescription = null)
+        },
+      )
+      NavigationBarItem(
+        selected = currentRoute is Routes.Deposit,
+        onClick = {
+          if (currentRoute is Routes.UserScope) {
+            backStack.remove(currentRoute)
+            backStack.add(
+              Routes.Deposit(currentRoute.serverId, currentRoute.userId)
+            )
+          }
+        },
+        icon = {
+          Icon(Icons.Outlined.LocalAtm, contentDescription = null)
+        },
+      )
+      NavigationBarItem(
+        selected = currentRoute is Routes.History,
+        onClick = {
+          if (currentRoute is Routes.UserScope) {
+            backStack.remove(currentRoute)
+            backStack.add(
+              Routes.History(currentRoute.serverId, currentRoute.userId)
+            )
+          }
+        },
+        icon = {
+          Icon(Icons.Outlined.History, contentDescription = null)
+        },
+      )
+      NavigationBarItem(
+        selected = currentRoute is Routes.Wrapped,
+        onClick = {
+          if (currentRoute is Routes.UserScope) {
+            backStack.remove(currentRoute)
+            backStack.add(
+              Routes.Wrapped(currentRoute.serverId, currentRoute.userId)
+            )
+          }
+        },
+        icon = {
+          Icon(Icons.Outlined.Celebration, contentDescription = null)
+        },
+      )
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAnimationContainer.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/DepositRoute.kt
similarity index 65%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAnimationContainer.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/DepositRoute.kt
index d05bf86d1823651e98768f108fa1f41151feb027..67eb64775cf2a8ba2ebf4fd2042cce24d834a092 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAnimationContainer.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/DepositRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,18 +22,24 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.navigation
+package de.chaosdorf.meteroid.ui
 
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.UserViewModel
 
 @Composable
-fun NavigationAnimationContainer(
-  visible: Boolean, content: @Composable () -> Unit
+fun DepositRoute(
+  viewModel: UserViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
 ) {
-  AnimatedVisibility(visible, enter = expandVertically(), exit = shrinkVertically()) {
-    content()
+  Column(Modifier.padding(contentPadding)) {
+    Text("Deposit ${viewModel.serverId} ${viewModel.userId}")
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/HistoryRoute.kt
similarity index 62%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryScreen.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/HistoryRoute.kt
index f6cbec97ea4118616f90cdf85328676da637fbcb..184bd4c52fbef6d41f0ac3be5930d5739172e03d 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryScreen.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/HistoryRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,28 +22,24 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home.transactionhistory
+package de.chaosdorf.meteroid.ui
 
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.UserViewModel
 
 @Composable
-fun TransactionHistoryScreen(
-  viewModel: TransactionHistoryViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
+fun HistoryRoute(
+  viewModel: UserViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
 ) {
-  val transactions by viewModel.transactions.collectAsState()
-
-  LazyColumn(contentPadding = contentPadding) {
-    items(
-      transactions,
-      key = { "transaction-${it.transaction.serverId}-${it.transaction.transactionId}" },
-    ) { (transaction, drink) ->
-      TransactionHistoryItem(transaction, drink)
-    }
+  Column(Modifier.padding(contentPadding)) {
+    Text("History ${viewModel.serverId} ${viewModel.userId}")
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidRouter.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidRouter.kt
deleted file mode 100644
index 45bba3a01fb16faed4fbb1c9e487303fe3981119..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidRouter.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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
-
-import android.util.Log
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.navigation.NavType
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navArgument
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.ui.home.deposit.DepositScreen
-import de.chaosdorf.meteroid.ui.home.deposit.DepositViewModel
-import de.chaosdorf.meteroid.ui.home.purchase.PurchaseScreen
-import de.chaosdorf.meteroid.ui.home.purchase.PurchaseViewModel
-import de.chaosdorf.meteroid.ui.home.transactionhistory.TransactionHistoryScreen
-import de.chaosdorf.meteroid.ui.home.transactionhistory.TransactionHistoryViewModel
-import de.chaosdorf.meteroid.ui.home.wrapped.WrappedScreen
-import de.chaosdorf.meteroid.ui.home.wrapped.WrappedViewModel
-import de.chaosdorf.meteroid.ui.navigation.NavigationViewModel
-import de.chaosdorf.meteroid.ui.servers.AddServerScreen
-import de.chaosdorf.meteroid.ui.servers.AddServerViewModel
-import de.chaosdorf.meteroid.ui.settings.SettingsScreen
-import de.chaosdorf.meteroid.ui.settings.SettingsViewModel
-import de.chaosdorf.meteroid.ui.userlist.UserListScreen
-import de.chaosdorf.meteroid.ui.userlist.UserListViewModel
-import de.chaosdorf.meteroid.util.toFancyString
-import kotlinx.coroutines.flow.collectLatest
-
-@Composable
-fun MeteroidRouter(
-  initialAccount: AccountPreferences.State,
-) {
-  val navController = rememberNavController()
-  val navigationViewModel = hiltViewModel<NavigationViewModel>()
-
-  LaunchedEffect(navController) {
-    navController.currentBackStack.collectLatest {
-      Log.i("MeteroidRouter", "Navigation: ${it.toFancyString()}")
-    }
-  }
-
-  MeteroidScaffold(navController, navigationViewModel) { paddingValues ->
-    NavHost(
-      navController = navController,
-      startDestination = MeteroidScreen.Home.Purchase.route,
-      enterTransition = { fadeIn() },
-      exitTransition = { fadeOut() },
-      popEnterTransition = { fadeIn() },
-      popExitTransition = { fadeOut() },
-    ) {
-      composable(
-        MeteroidScreen.Home.Purchase.route, arguments = listOf(
-          navArgument("server") {
-            type = NavType.LongType
-            defaultValue = initialAccount.server?.value ?: -1L
-          },
-          navArgument("user") {
-            type = NavType.LongType
-            defaultValue = initialAccount.user?.value ?: -1L
-          },
-        )
-      ) { entry ->
-        val serverId = entry.arguments?.getLong("server")?.let(::ServerId)
-          ?: ServerId(-1L)
-        val userId = entry.arguments?.getLong("user")?.let(::UserId)
-          ?: UserId(-1L)
-
-        LaunchedEffect(serverId, userId) {
-          if (!serverId.isValid() || !userId.isValid()) {
-            navigationViewModel.expanded.value = true
-          }
-        }
-
-        if (serverId.isValid() && userId.isValid()) {
-          val viewModel: PurchaseViewModel = hiltViewModel(
-            key = MeteroidScreen.Home.Purchase.build(serverId, userId)
-          )
-          PurchaseScreen(navController, viewModel, PaddingValues(top = 96.dp))
-        }
-      }
-      composable(
-        MeteroidScreen.Home.Deposit.route, arguments = listOf(
-          navArgument("server") {
-            type = NavType.LongType
-          },
-          navArgument("user") {
-            type = NavType.LongType
-          },
-        )
-      ) {
-        val viewModel: DepositViewModel = hiltViewModel()
-        DepositScreen(navController, viewModel, PaddingValues(top = 96.dp))
-      }
-      composable(
-        MeteroidScreen.Home.History.route, arguments = listOf(
-          navArgument("server") {
-            type = NavType.LongType
-          },
-          navArgument("user") {
-            type = NavType.LongType
-          },
-        )
-      ) {
-        val viewModel: TransactionHistoryViewModel = hiltViewModel()
-        TransactionHistoryScreen(viewModel, PaddingValues(top = 96.dp))
-      }
-      composable(
-        MeteroidScreen.Home.Wrapped.route, arguments = listOf(
-          navArgument("server") {
-            type = NavType.LongType
-          },
-          navArgument("user") {
-            type = NavType.LongType
-          },
-        )
-      ) {
-        val viewModel: WrappedViewModel = hiltViewModel()
-        WrappedScreen(viewModel, PaddingValues(top = 96.dp))
-      }
-      composable(
-        MeteroidScreen.UserList.route, arguments = listOf(
-          navArgument("server") {
-            type = NavType.LongType
-          },
-        )
-      ) {
-        val viewModel: UserListViewModel = hiltViewModel()
-        UserListScreen(navController, viewModel, PaddingValues(top = 96.dp))
-      }
-      composable(MeteroidScreen.AddServer.route) {
-        val viewModel: AddServerViewModel = hiltViewModel()
-        AddServerScreen(navController, viewModel, PaddingValues(top = 96.dp))
-      }
-      composable(MeteroidScreen.Settings.route) {
-        val viewModel: SettingsViewModel = hiltViewModel()
-        SettingsScreen(navController, viewModel, PaddingValues(top = 96.dp))
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt
deleted file mode 100644
index 19dbfe5a3d668f2f3db5dec983abd810b6028e56..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScaffold.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.navigation.NavController
-import de.chaosdorf.meteroid.ui.home.MeteroidBottomBar
-import de.chaosdorf.meteroid.ui.navigation.MeteroidNavigation
-import de.chaosdorf.meteroid.ui.navigation.NavigationScrim
-import de.chaosdorf.meteroid.ui.navigation.NavigationViewModel
-
-@Composable
-fun MeteroidScaffold(
-  navController: NavController,
-  viewModel: NavigationViewModel,
-  content: @Composable (PaddingValues) -> Unit
-) {
-  Column {
-    Surface(
-      modifier = Modifier
-        .fillMaxWidth()
-        .windowInsetsTopHeight(WindowInsets.statusBars),
-      color = MaterialTheme.colorScheme.scrim.copy(alpha = .38f),
-    ) {}
-    Box(Modifier.weight(1f)) {
-      Scaffold(
-        bottomBar = { MeteroidBottomBar(navController, viewModel) },
-        content = content
-      )
-      Surface(
-        modifier = Modifier
-          .align(Alignment.BottomCenter)
-          .fillMaxWidth()
-          .windowInsetsBottomHeight(WindowInsets.navigationBars),
-        color = MaterialTheme.colorScheme.scrim.copy(alpha = .38f),
-      ) {}
-      NavigationScrim(viewModel)
-      MeteroidNavigation(navController, viewModel)
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScreen.kt
deleted file mode 100644
index f0c27244cb65a1f7a7603b7813a9da5bd689704b..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/MeteroidScreen.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Celebration
-import androidx.compose.material.icons.outlined.History
-import androidx.compose.material.icons.outlined.LocalAtm
-import androidx.compose.material.icons.twotone.Celebration
-import androidx.compose.material.icons.twotone.History
-import androidx.compose.material.icons.twotone.LocalAtm
-import androidx.compose.ui.graphics.vector.ImageVector
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.theme.icons.MeteroidIcons
-import de.chaosdorf.meteroid.theme.icons.outlined.WaterFull
-import de.chaosdorf.meteroid.theme.icons.twotone.WaterFull
-
-sealed class MeteroidScreen(val route: String) {
-  sealed class Home(
-    val label: String,
-    val activeIcon: ImageVector,
-    val inactiveIcon: ImageVector,
-    route: String
-  ) : MeteroidScreen(route) {
-    data object Purchase : Home(
-      "Purchase",
-      MeteroidIcons.TwoTone.WaterFull,
-      MeteroidIcons.Outlined.WaterFull,
-      "server/{server}/user/{user}/purchase"
-    ) {
-      fun build(server: ServerId, user: UserId) = route
-        .replace("{server}", server.value.toString())
-        .replace("{user}", user.value.toString())
-    }
-
-    data object Deposit : Home(
-      "Deposit",
-      Icons.TwoTone.LocalAtm,
-      Icons.Outlined.LocalAtm,
-      "server/{server}/user/{user}/deposit"
-    ) {
-      fun build(server: ServerId, user: UserId) = route
-        .replace("{server}", server.value.toString())
-        .replace("{user}", user.value.toString())
-    }
-
-    data object History : Home(
-      "History",
-      Icons.TwoTone.History,
-      Icons.Outlined.History,
-      "server/{server}/user/{user}/history"
-    ) {
-      fun build(server: ServerId, user: UserId) = route
-        .replace("{server}", server.value.toString())
-        .replace("{user}", user.value.toString())
-    }
-
-    data object Wrapped : Home(
-      "Wrapped",
-      Icons.TwoTone.Celebration,
-      Icons.Outlined.Celebration,
-      "server/{server}/user/{user}/wrapped"
-    ) {
-      fun build(server: ServerId, user: UserId) = route
-        .replace("{server}", server.value.toString())
-        .replace("{user}", user.value.toString())
-    }
-  }
-
-  data object UserList : MeteroidScreen("server/{server}/userList") {
-    fun build(server: ServerId) = route
-      .replace("{server}", server.value.toString())
-  }
-
-  data object AddServer : MeteroidScreen("addServer") {
-    fun build() = route
-  }
-
-  data object Settings : MeteroidScreen("settings") {
-    fun build() = route
-  }
-
-  companion object {
-    fun byRoute(route: String?) = when (route) {
-      Home.Purchase.route -> Home.Purchase
-      Home.Deposit.route -> Home.Deposit
-      Home.History.route -> Home.History
-      Home.Wrapped.route -> Home.Wrapped
-      UserList.route -> UserList
-      AddServer.route -> AddServer
-      Settings.route -> Settings
-      else -> null
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/MonetaryAmount.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PurchaseRoute.kt
similarity index 61%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/MonetaryAmount.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/PurchaseRoute.kt
index a38f63060a6a2390be36737fe162872582545149..7a56bd15e96da89e27388f5320b75d17c0a0d342 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/MonetaryAmount.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PurchaseRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,18 +22,24 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home.deposit
+package de.chaosdorf.meteroid.ui
 
-import androidx.annotation.DrawableRes
-import de.chaosdorf.meteroid.R
-import java.math.BigDecimal
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.UserViewModel
 
-enum class MonetaryAmount(val amount: BigDecimal, @DrawableRes val image: Int) {
-  MONEY_50(0.50.toBigDecimal(), R.drawable.euro_50),
-  MONEY_100(1.00.toBigDecimal(), R.drawable.euro_100),
-  MONEY_200(2.00.toBigDecimal(), R.drawable.euro_200),
-  MONEY_500(5.00.toBigDecimal(), R.drawable.euro_500),
-  MONEY_1000(10.00.toBigDecimal(), R.drawable.euro_1000),
-  MONEY_2000(20.00.toBigDecimal(), R.drawable.euro_2000),
-  MONEY_5000(50.00.toBigDecimal(), R.drawable.euro_5000),
+@Composable
+fun PurchaseRoute(
+  viewModel: UserViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  Column(Modifier.padding(contentPadding)) {
+    Text("Purchase ${viewModel.serverId} ${viewModel.userId}")
+  }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationScrim.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt
similarity index 50%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationScrim.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt
index 265d989aadbda4586a144df70bfbcf1de0933a73..dacce6eb0bfc45c278c2bda6c3170d645c7054ad 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationScrim.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/Routes.kt
@@ -22,30 +22,57 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.navigation
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-
-@Composable
-fun NavigationScrim(viewModel: NavigationViewModel) {
-  val expanded by viewModel.expanded.collectAsState()
-
-  AnimatedVisibility(expanded, enter = fadeIn(), exit = fadeOut()) {
-    Surface(
-      color = MaterialTheme.colorScheme.scrim.copy(alpha = 0.2f),
-      modifier = Modifier.fillMaxSize().clickable {
-        viewModel.expanded.value = false
-      }
-    ) {}
+package de.chaosdorf.meteroid.ui
+
+import androidx.navigation3.runtime.NavKey
+import de.chaosdorf.mete.model.ServerId
+import de.chaosdorf.mete.model.UserId
+import kotlinx.serialization.Serializable
+
+sealed interface Routes : NavKey {
+  interface ServerScope {
+    val serverId: ServerId
+  }
+
+  interface UserScope : ServerScope {
+    val userId: UserId
   }
+
+  @Serializable
+  data object Setup : Routes
+
+  @Serializable
+  data object Settings : Routes
+
+  @Serializable
+  data object ServerList : Routes
+
+  @Serializable
+  data class UserList(
+    override val serverId: ServerId
+  ) : Routes, ServerScope
+
+  @Serializable
+  data class Purchase(
+    override val serverId: ServerId,
+    override val userId: UserId
+  ) : Routes, UserScope
+
+  @Serializable
+  data class Deposit(
+    override val serverId: ServerId,
+    override val userId: UserId
+  ) : Routes, UserScope
+
+  @Serializable
+  data class History(
+    override val serverId: ServerId,
+    override val userId: UserId
+  ) : Routes, UserScope
+
+  @Serializable
+  data class Wrapped(
+    override val serverId: ServerId,
+    override val userId: UserId
+  ) : Routes, UserScope
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/ServerListRoute.kt
similarity index 55%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/ServerListRoute.kt
index 20344554c820e7ab22adae06940ed3e34b572d2b..b5aefc1b68b149607fc0ad6f1d33006132563330 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/ServerListRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,35 +22,42 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.navigation
+package de.chaosdorf.meteroid.ui
 
-import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.material3.Icon
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.Text
 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 androidx.compose.ui.unit.dp
-import de.chaosdorf.meteroid.ui.AvatarLayout
+import androidx.navigation3.runtime.NavBackStack
+import coil3.compose.AsyncImage
+import de.chaosdorf.meteroid.viewmodel.ServerListViewModel
 
 @Composable
-fun NavigationSettingsItem(expanded: Boolean, onExpand: () -> Unit, onClick: () -> Unit) {
-  val height: Dp by animateDpAsState(if (expanded) 48.dp else 64.dp, label = "height")
+fun ServerListRoute(
+  viewModel: ServerListViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  val servers by viewModel.servers.collectAsState()
 
-  ListItem(
-    headlineContent = { Text("Settings") },
-    leadingContent = {
-      AvatarLayout {
-        Icon(Icons.Filled.Settings, contentDescription = null)
-      }
-    },
-    modifier = Modifier.requiredHeight(height)
-      .clickable(onClick = if (expanded) onClick else onExpand)
-  )
+  LazyColumn(contentPadding = contentPadding) {
+    items(servers, { "server-${it.serverId}" }) { server ->
+      ListItem(
+        headlineContent = { Text(server.name ?: "Unknown") },
+        supportingContent = { Text(server.url) },
+        leadingContent = if (server.logoUrl != null) {
+          @Composable { AsyncImage(server.logoUrl, contentDescription = null) }
+        } else null,
+        modifier = Modifier.clickable {
+          backStack.add(Routes.UserList(server.serverId))
+        },
+      )
+    }
+  }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/SettingsRoute.kt
similarity index 79%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsScreen.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/SettingsRoute.kt
index 898ff670076a1d846e7f631e71414820498bc5d8..6065bc8d0d414f5e9043022ca32adf81301a6901 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsScreen.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/SettingsRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,7 +22,7 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.settings
+package de.chaosdorf.meteroid.ui
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -30,16 +30,16 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.SettingsViewModel
 
 @Composable
-fun SettingsScreen(navController: NavController, viewModel: SettingsViewModel, contentPadding: PaddingValues) {
-  Column(
-    Modifier
-      .padding(contentPadding)
-      .padding(16.dp, 8.dp)
-  ) {
+fun SettingsRoute(
+  viewModel: SettingsViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  Column(Modifier.padding(contentPadding)) {
     Text("Settings")
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/SetupRoute.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/SetupRoute.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a1b380555f708469f1b8a7a275d798c2f5918f3
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/SetupRoute.kt
@@ -0,0 +1,78 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.SetupViewModel
+import kotlinx.coroutines.launch
+
+@Composable
+fun SetupRoute(
+  viewModel: SetupViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  val coroutineScope = rememberCoroutineScope()
+  val serverUrl by viewModel.serverUrl.collectAsState()
+  val error by viewModel.error.collectAsState()
+
+  Column(Modifier.padding(contentPadding)) {
+    error?.let {
+      Surface(
+        color = MaterialTheme.colorScheme.errorContainer,
+        contentColor = MaterialTheme.colorScheme.onErrorContainer,
+      ) {
+        Text(it)
+      }
+    }
+    TextField(
+      value = serverUrl,
+      onValueChange = { viewModel.serverUrl.value = it },
+    )
+    Button(
+      onClick = {
+        coroutineScope.launch {
+          val server = viewModel.add()
+          if (server != null) {
+            backStack.removeLastOrNull()
+            backStack.add(Routes.ServerList)
+            backStack.add(Routes.UserList(server.serverId))
+          }
+        }
+      }
+    ) {
+      Text("Save")
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/TopBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/TopBar.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3bdf179d882ef73c0f47fb2540b17250ec58ba8f
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/TopBar.kt
@@ -0,0 +1,161 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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
+
+import androidx.compose.animation.*
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.mete.model.ServerId
+import de.chaosdorf.mete.model.UserId
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun TopBar(backStack: NavBackStack) {
+
+  val currentRoute = backStack.lastOrNull()
+
+  if (currentRoute !is Routes.Setup) {
+    var open by remember { mutableStateOf(false) }
+    val transition = updateTransition(open, label = "transition")
+
+    val backgroundAlpha = transition.animateFloat { if (it) .2f else 0f }
+    val elevation = transition.animateDp { if (it) 0.dp else 4.dp }
+
+    Surface(
+      modifier = Modifier
+        .windowInsetsPadding(WindowInsets.statusBars)
+        .padding(8.dp)
+        .height(56.dp),
+      shape = RoundedCornerShape(28.dp),
+      shadowElevation = elevation.value,
+      tonalElevation = 4.dp,
+      onClick = { open = true }
+    ) {
+      Row(
+        modifier = Modifier
+          .fillMaxSize()
+          .padding(horizontal = 16.dp),
+        verticalAlignment = Alignment.CenterVertically
+      ) {
+        Text(currentRoute.toString())
+      }
+    }
+
+    if (open || transition.currentState || transition.isRunning) {
+      Dialog(
+        onDismissRequest = { open = false },
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+      ) {
+        Box(
+          Modifier
+            .fillMaxSize()
+            .background(MaterialTheme.colorScheme.scrim.copy(alpha = backgroundAlpha.value))
+            .clickable { open = false }
+        ) {
+          Surface(
+            modifier = Modifier
+              .windowInsetsPadding(WindowInsets.safeDrawing)
+              .padding(8.dp),
+            shape = RoundedCornerShape(28.dp),
+            shadowElevation = 4.dp - elevation.value,
+            tonalElevation = 4.dp,
+          ) {
+            Column {
+              transition.AnimatedVisibility(
+                visible = { it || currentRoute == Routes.ServerList },
+                enter = expandVertically() + fadeIn(),
+                exit = shrinkVertically() + fadeOut(),
+              ) {
+                Row(
+                  modifier = Modifier
+                    .fillMaxWidth()
+                    .height(56.dp)
+                    .clickable {
+                      backStack.add(Routes.ServerList)
+                      open = false
+                    }.padding(horizontal = 16.dp),
+                  verticalAlignment = Alignment.CenterVertically
+                ) {
+                  Text("ServerList")
+                }
+              }
+              transition.AnimatedVisibility(
+                visible = { it || (currentRoute is Routes.UserScope && currentRoute.serverId == ServerId(1) && currentRoute.userId == UserId(1)) },
+                enter = expandVertically() + fadeIn(),
+                exit = shrinkVertically() + fadeOut(),
+              ) {
+                Row(
+                  modifier = Modifier
+                    .fillMaxWidth()
+                    .height(56.dp)
+                    .clickable {
+                      backStack.add(Routes.Purchase(ServerId(1), UserId(1)))
+                      open = false
+                    }.padding(horizontal = 16.dp),
+                  verticalAlignment = Alignment.CenterVertically
+                ) {
+                  Text("User(ServerId(1),UserId(1))")
+                }
+              }
+              transition.AnimatedVisibility(
+                visible = { it || currentRoute == Routes.Settings },
+                enter = expandVertically() + fadeIn(),
+                exit = shrinkVertically() + fadeOut(),
+              ) {
+                Row(
+                  modifier = Modifier
+                    .fillMaxWidth()
+                    .height(56.dp)
+                    .clickable {
+                      backStack.add(Routes.Settings)
+                      open = false
+                    }.padding(horizontal = 16.dp),
+                  verticalAlignment = Alignment.CenterVertically
+                ) {
+                  Text("Settings")
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/UserListRoute.kt
similarity index 55%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/UserListRoute.kt
index 9bc43f5e0994141974f668904b40c6b8c989b2b3..594c9a826e26633bf48fa9a304f4ff981acaf8b7 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/UserListRoute.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,35 +22,42 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.navigation
+package de.chaosdorf.meteroid.ui
 
-import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material3.Icon
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.Text
 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 androidx.compose.ui.unit.dp
-import de.chaosdorf.meteroid.ui.AvatarLayout
+import androidx.navigation3.runtime.NavBackStack
+import coil3.compose.AsyncImage
+import de.chaosdorf.meteroid.viewmodel.UserListViewModel
 
 @Composable
-fun NavigationAddServerItem(expanded: Boolean, onExpand: () -> Unit, onClick: () -> Unit) {
-  val height: Dp by animateDpAsState(if (expanded) 48.dp else 64.dp, label = "height")
+fun UserListRoute(
+  viewModel: UserListViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  val users by viewModel.users.collectAsState()
 
-  ListItem(
-    headlineContent = { Text("Add Server") },
-    leadingContent = {
-      AvatarLayout {
-        Icon(Icons.Default.Add, contentDescription = null)
-      }
-    },
-    modifier = Modifier.requiredHeight(height)
-      .clickable(onClick = if (expanded) onClick else onExpand)
-  )
+  LazyColumn(contentPadding = contentPadding) {
+    items(users, { "server-${it.serverId}/user-${it.userId}"}) {
+      ListItem(
+        headlineContent = { Text(it.name) },
+        supportingContent = { Text(it.email ?: "") },
+        leadingContent = if (it.gravatarUrl != null) {
+          @Composable { AsyncImage(it.gravatarUrl, contentDescription = null) }
+        } else null,
+        modifier = Modifier.clickable {
+          backStack.add(Routes.Purchase(it.serverId, it.userId))
+        }
+      )
+    }
+  }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/WrappedRoute.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/WrappedRoute.kt
new file mode 100644
index 0000000000000000000000000000000000000000..52ecd9713421f0dfef05ee9c63071f3f96d4f8f2
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/WrappedRoute.kt
@@ -0,0 +1,45 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation3.runtime.NavBackStack
+import de.chaosdorf.meteroid.viewmodel.WrappedViewModel
+
+@Composable
+fun WrappedRoute(
+  viewModel: WrappedViewModel,
+  backStack: NavBackStack,
+  contentPadding: PaddingValues,
+) {
+  Column(Modifier.padding(contentPadding)) {
+    Text("Wrapped ${viewModel.serverId} ${viewModel.userId}")
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBar.kt
deleted file mode 100644
index 789047bc7d895b5c7403e9d3082477007ba0d75c..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBar.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.home
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationBar
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.navigation.NavController
-import androidx.navigation.compose.currentBackStackEntryAsState
-import de.chaosdorf.meteroid.ui.MeteroidScreen
-import de.chaosdorf.meteroid.ui.navigation.NavigationViewModel
-import de.chaosdorf.meteroid.util.findStartDestination
-import kotlinx.datetime.Clock
-import kotlinx.datetime.TimeZone
-import kotlinx.datetime.toLocalDateTime
-
-@Composable
-fun MeteroidBottomBar(navController: NavController, viewModel: NavigationViewModel) {
-  val backStackEntry by navController.currentBackStackEntryAsState()
-  val activeRoute = MeteroidScreen.byRoute(backStackEntry?.destination?.route)
-
-  val account by viewModel.account.collectAsState()
-  val server = account?.server
-  val user = account?.user
-  val historyDisabled by viewModel.historyDisabled.collectAsState()
-  val wrappedEnabled = Clock.System.now()
-    .toLocalDateTime(TimeZone.currentSystemDefault())
-    .month.let { it == java.time.Month.DECEMBER }
-
-  AnimatedVisibility(
-    activeRoute is MeteroidScreen.Home && server != null && user != null,
-    enter = slideInVertically(initialOffsetY = { it }),
-    exit = slideOutVertically(targetOffsetY = { it })
-  ) {
-    NavigationBar(
-      contentColor = MaterialTheme.colorScheme.primary
-    ) {
-      MeteroidBottomBarItem(
-        MeteroidScreen.Home.Purchase,
-        activeRoute == MeteroidScreen.Home.Purchase,
-      ) {
-        if (server != null && user != null) {
-          navController.navigate(MeteroidScreen.Home.Purchase.build(server, user)) {
-            launchSingleTop = true
-            restoreState = false
-            popUpTo(findStartDestination(navController.graph).id) {
-              saveState = false
-            }
-          }
-        }
-      }
-
-      MeteroidBottomBarItem(
-        MeteroidScreen.Home.Deposit,
-        activeRoute == MeteroidScreen.Home.Deposit,
-      ) {
-        if (server != null && user != null) {
-          navController.navigate(MeteroidScreen.Home.Deposit.build(server, user)) {
-            launchSingleTop = true
-            restoreState = false
-            popUpTo(findStartDestination(navController.graph).id) {
-              saveState = false
-            }
-          }
-        }
-      }
-
-      if (!historyDisabled) {
-        MeteroidBottomBarItem(
-          MeteroidScreen.Home.History,
-          activeRoute == MeteroidScreen.Home.History,
-        ) {
-          if (server != null && user != null) {
-            navController.navigate(MeteroidScreen.Home.History.build(server, user)) {
-              launchSingleTop = true
-              restoreState = false
-              popUpTo(findStartDestination(navController.graph).id) {
-                saveState = false
-              }
-            }
-          }
-        }
-
-        if (wrappedEnabled) {
-          MeteroidBottomBarItem(
-            MeteroidScreen.Home.Wrapped,
-            activeRoute == MeteroidScreen.Home.Wrapped,
-          ) {
-            if (server != null && user != null) {
-              navController.navigate(MeteroidScreen.Home.Wrapped.build(server, user)) {
-                launchSingleTop = true
-                restoreState = false
-                popUpTo(findStartDestination(navController.graph).id) {
-                  saveState = false
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBarItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBarItem.kt
deleted file mode 100644
index 052ee599f2f4806582776a3480ed278fe52b049a..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/MeteroidBottomBarItem.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.home
-
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import de.chaosdorf.meteroid.theme.onPrimaryContainerTinted
-import de.chaosdorf.meteroid.ui.MeteroidScreen
-
-@Composable
-fun RowScope.MeteroidBottomBarItem(
-  route: MeteroidScreen.Home,
-  active: Boolean,
-  onClick: () -> Unit,
-) {
-  NavigationBarItem(
-    icon = {
-      Icon(
-        if (active) route.activeIcon else route.inactiveIcon,
-        contentDescription = null,
-        tint = MaterialTheme.colorScheme.onPrimaryContainerTinted
-      )
-    },
-    label = { Text(route.label, color = MaterialTheme.colorScheme.onPrimaryContainerTinted) },
-    selected = active,
-    onClick = onClick,
-    colors = NavigationBarItemDefaults.colors(
-      indicatorColor = MaterialTheme.colorScheme.primaryContainer
-    )
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/PriceBadge.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/PriceBadge.kt
deleted file mode 100644
index 8a90ba385131642c3f0323287f0be815d4ebffd6..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/PriceBadge.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.home
-
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Badge
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.dp
-import java.math.BigDecimal
-
-@Composable
-fun PriceBadge(
-  price: BigDecimal,
-  modifier: Modifier = Modifier,
-  containerColor: Color =
-    if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.primary
-    else MaterialTheme.colorScheme.error,
-  textColor: Color =
-    if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.onPrimary
-    else MaterialTheme.colorScheme.onError,
-  textStyle: TextStyle = MaterialTheme.typography.labelLarge
-) {
-  Badge(
-    containerColor = containerColor,
-    modifier = modifier
-  ) {
-    Text(
-      "%.02f €".format(price),
-      style = textStyle,
-      color = textColor,
-      modifier = Modifier.padding(8.dp, 4.dp)
-    )
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositMoneyItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositMoneyItem.kt
deleted file mode 100644
index 1e83c4f935582132652c5ee88f214fa9abc81047..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositMoneyItem.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.home.deposit
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-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.unit.dp
-import de.chaosdorf.meteroid.ui.home.PriceBadge
-
-@Composable
-fun DepositMoneyItem(
-  item: MonetaryAmount,
-  modifier: Modifier = Modifier,
-  onDeposit: (MonetaryAmount) -> Unit = {}
-) {
-  Box(
-    modifier = modifier
-      .height(IntrinsicSize.Max)
-      .clip(RoundedCornerShape(8.dp))
-      .clickable { onDeposit(item) }
-  ) {
-    Image(
-      painterResource(item.image),
-      contentDescription = null,
-      contentScale = ContentScale.Fit,
-      modifier = Modifier
-        .aspectRatio(1.0f)
-    )
-    PriceBadge(
-      item.amount,
-      modifier = Modifier
-        .align(Alignment.BottomEnd)
-        .paddingFromBaseline(bottom = 24.dp)
-    )
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositScreen.kt
deleted file mode 100644
index 339520e5d35c339c0fd71585893adf9e37f1d861..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositScreen.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.home.deposit
-
-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.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-
-@Composable
-fun DepositScreen(
-  navController: NavController,
-  viewModel: DepositViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
-) {
-  LazyVerticalGrid(
-    GridCells.Adaptive(104.dp),
-    contentPadding = contentPadding,
-    modifier = Modifier.padding(horizontal = 8.dp),
-  ) {
-    items(
-      viewModel.money,
-      key = { "deposit-${it.ordinal}" },
-    ) { monetaryAmount ->
-      DepositMoneyItem(monetaryAmount) {
-        viewModel.deposit(it, navController::navigateUp)
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseDrinkTile.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseDrinkTile.kt
deleted file mode 100644
index d3ca74c4b4ba60feec86aed15e62377910c98ef7..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseDrinkTile.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.home.purchase
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-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.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-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.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import coil3.compose.rememberAsyncImagePainter
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.theme.secondaryGradient
-import de.chaosdorf.meteroid.ui.home.PriceBadge
-import kotlinx.coroutines.delay
-import java.math.BigDecimal
-
-@Composable
-fun PurchaseDrinkTile(
-  item: Drink,
-  modifier: Modifier = Modifier,
-  onPurchase: (Drink, Int) -> Unit = { _, _ -> }
-) {
-  var purchaseCount by remember { mutableStateOf(0) }
-  val pendingPurchases = purchaseCount != 0
-
-  LaunchedEffect(purchaseCount) {
-    delay(2000L)
-    onPurchase(item, purchaseCount)
-    purchaseCount = 0
-  }
-
-  val thumbPainter = rememberAsyncImagePainter(
-    item.logoUrl
-  )
-  val drinkPainter = rememberAsyncImagePainter(
-    item.originalLogoUrl,
-    error = thumbPainter
-  )
-
-  Column(
-    modifier = modifier
-      .height(IntrinsicSize.Max)
-      .alpha(if (item.active) 1.0f else 0.67f)
-      .clip(RoundedCornerShape(8.dp))
-      .clickable { purchaseCount += 1 }
-      .padding(8.dp)
-  ) {
-    Box(
-      Modifier.aspectRatio(1.0f)
-        .background(MaterialTheme.colorScheme.secondaryGradient.verticalGradient(), CircleShape),
-      contentAlignment = Alignment.Center
-    ) {
-      Image(
-        drinkPainter,
-        contentDescription = null,
-        contentScale = ContentScale.Fit,
-        modifier = Modifier.alpha(if (pendingPurchases) 0.0f else 1.0f)
-          .clip(CircleShape)
-      )
-      PriceBadge(
-        item.price,
-        modifier = Modifier
-          .alpha(if (pendingPurchases) 0.0f else 1.0f)
-          .align(Alignment.BottomEnd)
-          .paddingFromBaseline(bottom = 12.dp)
-      )
-      Text(
-        "×$purchaseCount",
-        fontSize = 36.sp,
-        fontWeight = FontWeight.Light,
-        color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.67f),
-        textAlign = TextAlign.Center,
-        modifier = Modifier.alpha(if (pendingPurchases) 1.0f else 0.0f)
-      )
-    }
-    Spacer(Modifier.height(4.dp))
-    Text(
-      item.name,
-      modifier = Modifier
-        .fillMaxWidth()
-        .padding(horizontal = 8.dp),
-      textAlign = TextAlign.Center,
-      fontWeight = FontWeight.SemiBold,
-      style = MaterialTheme.typography.labelLarge,
-    )
-    Spacer(Modifier.height(4.dp))
-    Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
-      val unitPrice =
-        if (item.volume <= BigDecimal.ZERO) null
-        else item.price / item.volume
-
-      Text(
-        if (unitPrice == null) String.format("%.02fl", item.volume)
-        else String.format("%.02fl · %.02f€/l", item.volume, item.price / item.volume),
-        modifier = Modifier
-          .fillMaxWidth()
-          .padding(horizontal = 8.dp),
-        textAlign = TextAlign.Center,
-        fontWeight = FontWeight.SemiBold,
-        style = MaterialTheme.typography.labelMedium,
-        color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
-      )
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseFilterChip.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseFilterChip.kt
deleted file mode 100644
index 807b2eca4e238fd1b3feb725f2694e6358ac9f1f..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseFilterChip.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.home.purchase
-
-import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun PurchaseFilterChip(
-  label: String,
-  selected: Boolean,
-  onClick: () -> Unit,
-) {
-  FilterChip(
-    label = {
-      Text(label, style = MaterialTheme.typography.labelLarge)
-    },
-    selected = selected,
-    leadingIcon = {
-      if (selected) {
-        Icon(
-          Icons.Default.Check,
-          contentDescription = null,
-          modifier = Modifier.size(18.dp)
-        )
-      }
-    },
-    onClick = onClick,
-    colors = FilterChipDefaults.filterChipColors(
-      selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer
-    )
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseScreen.kt
deleted file mode 100644
index 74f49ebbd24c61947207cae0fd3e3814200b7738..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseScreen.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.home.purchase
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
-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 androidx.navigation.NavController
-
-@OptIn(ExperimentalLayoutApi::class)
-@Composable
-fun PurchaseScreen(
-  navController: NavController,
-  viewModel: PurchaseViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
-) {
-  val drinks by viewModel.drinks.collectAsState()
-  val filters by viewModel.filters.collectAsState()
-
-  LazyVerticalGrid(
-    GridCells.Adaptive(104.dp),
-    contentPadding = contentPadding,
-    modifier = Modifier.padding(horizontal = 8.dp),
-  ) {
-    item("filter", span = { GridItemSpan(maxLineSpan) }) {
-      FlowRow(
-        modifier = Modifier.padding(horizontal = 16.dp),
-        horizontalArrangement = Arrangement.spacedBy(8.dp)
-      ) {
-        PurchaseFilterChip(
-          label = "Active",
-          selected = filters.contains(PurchaseViewModel.Filter.Active),
-          onClick = { viewModel.toggleFilter(PurchaseViewModel.Filter.Active) }
-        )
-        PurchaseFilterChip(
-          label = "Coffeine Free",
-          selected = filters.contains(PurchaseViewModel.Filter.CaffeineFree),
-          onClick = { viewModel.toggleFilter(PurchaseViewModel.Filter.CaffeineFree) }
-        )
-      }
-    }
-
-    items(
-      drinks,
-      key = { "drink-${it.serverId}-${it.drinkId}" },
-    ) { drink ->
-      PurchaseDrinkTile(drink) { item, count ->
-        viewModel.purchase(item, count, navController::navigateUp)
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseViewModel.kt
deleted file mode 100644
index 16302040e89779d76082a852e612071d6bc435f9..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/purchase/PurchaseViewModel.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.home.purchase
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.model.DrinkRepository
-import de.chaosdorf.meteroid.sync.AccountProvider
-import de.chaosdorf.meteroid.sync.SyncManager
-import de.chaosdorf.meteroid.util.update
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class PurchaseViewModel @Inject constructor(
-  private val savedStateHandle: SavedStateHandle,
-  private val accountProvider: AccountProvider,
-  private val syncManager: SyncManager,
-  drinkRepository: DrinkRepository,
-) : ViewModel() {
-  val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
-  val userId = UserId(checkNotNull(savedStateHandle["user"]))
-
-  val filters: StateFlow<Set<Filter>> =
-    savedStateHandle.getStateFlow("filters", setOf(Filter.Active))
-
-  val drinks: StateFlow<List<Drink>> = combine(
-    drinkRepository.getAllFlow(serverId),
-    filters
-  ) { drinks, filters ->
-    drinks.filter { item ->
-      filters.all { filter -> filter.matches(item) }
-    }
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  fun toggleFilter(filter: Filter) {
-    savedStateHandle.update<Set<Filter>>("filters", emptySet()) { filters ->
-      if (filters.contains(filter)) filters - filter
-      else filters + filter
-    }
-  }
-
-  fun purchase(item: Drink, count: Int, onBack: () -> Unit) {
-    viewModelScope.launch {
-      accountProvider.account(serverId, userId)?.let { account ->
-        syncManager.purchase(account, item, count)
-        if (!account.pinned) {
-          onBack()
-        }
-      }
-    }
-  }
-
-  fun sync() {
-    viewModelScope.launch {
-      accountProvider.account(serverId, userId)?.let { account ->
-        syncManager.sync(account.server, account.user, incremental = true)
-      }
-    }
-  }
-
-  enum class Filter {
-    CaffeineFree,
-    Active;
-
-    fun matches(item: Drink) = when (this) {
-      CaffeineFree -> item.caffeine == 0
-      Active -> item.active
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryItem.kt
deleted file mode 100644
index 968d8fc7968b8824600087e2599f2af1d2809553..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryItem.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.home.transactionhistory
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-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.unit.dp
-import coil3.compose.rememberAsyncImagePainter
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.model.Transaction
-import de.chaosdorf.meteroid.theme.secondaryGradient
-import de.chaosdorf.meteroid.ui.home.PriceBadge
-import kotlinx.datetime.TimeZone
-import kotlinx.datetime.toJavaLocalDateTime
-import kotlinx.datetime.toLocalDateTime
-import java.math.BigDecimal
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
-
-@Composable
-fun TransactionHistoryItem(
-  transaction: Transaction,
-  drink: Drink?,
-  modifier: Modifier = Modifier
-) {
-  val timestamp = transaction.timestamp.toLocalDateTime(TimeZone.currentSystemDefault())
-  val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
-
-  ListItem(
-    headlineContent = {
-      val label = when {
-        drink != null -> drink.name
-        transaction.difference > BigDecimal.ZERO -> "Deposit"
-        else -> "Unknown"
-      }
-      Text(label)
-    },
-    supportingContent = {
-      Text(formatter.format(timestamp.toJavaLocalDateTime()))
-    },
-    leadingContent = {
-      Box(
-        modifier = Modifier
-          .size(48.dp)
-          .clip(CircleShape)
-          .aspectRatio(1.0f)
-          .background(MaterialTheme.colorScheme.secondaryGradient.verticalGradient())
-      ) {
-        if (drink != null) {
-          val thumbPainter = rememberAsyncImagePainter(
-            drink.logoUrl
-          )
-          val originalPainter = rememberAsyncImagePainter(
-            drink.originalLogoUrl,
-            error = thumbPainter
-          )
-
-          Image(
-            painter = originalPainter,
-            contentDescription = null,
-            contentScale = ContentScale.Fit,
-            modifier = Modifier
-              .align(Alignment.Center)
-              .fillMaxSize()
-          )
-        } else if (transaction.difference > BigDecimal.ZERO) {
-          Icon(
-            Icons.Default.AttachMoney,
-            contentDescription = null,
-            modifier = Modifier.align(Alignment.Center)
-          )
-        } else {
-          Icon(
-            Icons.Default.QuestionMark,
-            contentDescription = null,
-            modifier = Modifier.align(Alignment.Center)
-          )
-        }
-      }
-    },
-    trailingContent = {
-      PriceBadge(
-        transaction.difference,
-        modifier = Modifier.padding(horizontal = 8.dp)
-      )
-    },
-    modifier = modifier,
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryViewModel.kt
deleted file mode 100644
index 0ad8aacac4d956c84fd73bac2d1143fe576b6311..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionHistoryViewModel.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.home.transactionhistory
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.DrinkRepository
-import de.chaosdorf.meteroid.model.TransactionRepository
-import de.chaosdorf.meteroid.sync.AccountProvider
-import de.chaosdorf.meteroid.sync.SyncManager
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.launch
-import java.math.BigDecimal
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.minutes
-
-@HiltViewModel
-class TransactionHistoryViewModel @Inject constructor(
-  savedStateHandle: SavedStateHandle,
-  private val accountProvider: AccountProvider,
-  private val syncManager: SyncManager,
-  repository: TransactionRepository,
-  drinkRepository: DrinkRepository
-) : ViewModel() {
-  private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
-  private val userId = UserId(checkNotNull(savedStateHandle["user"]))
-
-  val transactions: StateFlow<List<TransactionInfo>> = combine(
-    repository.getAllFlow(serverId, userId), drinkRepository.getAllFlow(serverId)
-  ) { transactions, drinks ->
-    transactions.map { transaction ->
-      TransactionInfo(transaction,
-        drinks.firstOrNull { drink -> drink.drinkId == transaction.drinkId })
-    }
-  }.mapLatest { list ->
-    list.mergeAdjecentDeposits().filter {
-      it.drink != null || it.transaction.difference.abs() >= 0.01.toBigDecimal()
-    }
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  fun sync() {
-    viewModelScope.launch {
-      accountProvider.account(serverId, userId)?.let { account ->
-        syncManager.sync(account.server, account.user, incremental = true)
-      }
-    }
-  }
-}
-
-fun List<TransactionInfo>.mergeAdjecentDeposits(): List<TransactionInfo> {
-  val result = mutableListOf<TransactionInfo>()
-  for (entry in this) {
-    val previous = result.lastOrNull()
-    if (previous != null && previous.transaction.difference > BigDecimal.ZERO && entry.transaction.difference > BigDecimal.ZERO && previous.drink == null && entry.drink == null && entry.transaction.timestamp.minus(
-        previous.transaction.timestamp
-      ) < 5.minutes
-    ) {
-      result.removeLast()
-      result.add(
-        entry.copy(
-          transaction = entry.transaction.copy(
-            difference = entry.transaction.difference + previous.transaction.difference
-          )
-        )
-      )
-    } else {
-      result.add(entry)
-    }
-  }
-  return result
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionInfo.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionInfo.kt
deleted file mode 100644
index 15e2038601aca582d78b1afe9f3e58be3254907d..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/transactionhistory/TransactionInfo.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.home.transactionhistory
-
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.model.Transaction
-
-data class TransactionInfo(
-  val transaction: Transaction,
-  val drink: Drink?
-)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedScreen.kt
deleted file mode 100644
index 9aaa2b41bb1f0eef3c24d3a50de2a5aebd2d0ea9..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedScreen.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.home.wrapped
-
-import android.os.Build.VERSION
-import android.os.Build.VERSION_CODES
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
-import coil3.compose.rememberAsyncImagePainter
-import de.chaosdorf.meteroid.R
-import java.time.format.TextStyle
-
-@Composable
-fun WrappedScreen(
-  viewModel: WrappedViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
-) {
-  val slides by viewModel.slides.collectAsState()
-  @Suppress("DEPRECATION")
-  val locale = LocalConfiguration.current.let {
-    if (VERSION.SDK_INT >= VERSION_CODES.N) it.locales.get(0)
-    else it.locale
-  }
-
-  LazyColumn(contentPadding = contentPadding) {
-    items(
-      slides,
-      key = { "wrapped-${it::class.simpleName}" },
-    ) { slide ->
-      when (slide) {
-        is WrappedSlide.MostBoughtDrink ->
-          ListItem(
-            headlineContent = {
-              Text("Your favorite drink is ${slide.drink.name}")
-            },
-            supportingContent = {
-              Text("At least you enjoyed it ${slide.count} times this year.")
-            },
-            leadingContent = {
-              val thumbPainter = rememberAsyncImagePainter(
-                slide.drink.logoUrl
-              )
-              val drinkPainter = rememberAsyncImagePainter(
-                slide.drink.originalLogoUrl,
-                error = thumbPainter
-              )
-
-              Image(
-                drinkPainter,
-                contentDescription = null,
-                contentScale = ContentScale.Fit,
-                modifier = Modifier.size(72.dp)
-              )
-            }
-          )
-
-        is WrappedSlide.Caffeine ->
-          ListItem(
-            headlineContent = {
-              Text("You consumed ${slide.total} mg of caffeine this year.")
-            },
-            supportingContent = {
-              slide.wouldKill?.let { animal ->
-                Text("This could kill a medium-weight ${animal.name}. Glad you're still here.")
-              }
-            },
-            leadingContent = {
-              val painter = painterResource(
-                when (slide.wouldKill) {
-                  WrappedSlide.Caffeine.Animal.Squirrel -> R.drawable.wrapped_squirrel
-                  WrappedSlide.Caffeine.Animal.Rat -> R.drawable.wrapped_rat
-                  WrappedSlide.Caffeine.Animal.Cat -> R.drawable.wrapped_cat
-                  WrappedSlide.Caffeine.Animal.Koala -> R.drawable.wrapped_koala
-                  WrappedSlide.Caffeine.Animal.Lynx -> R.drawable.wrapped_lynx
-                  WrappedSlide.Caffeine.Animal.Jaguar -> R.drawable.wrapped_jaguar
-                  WrappedSlide.Caffeine.Animal.Reindeer -> R.drawable.wrapped_reindeer
-                  WrappedSlide.Caffeine.Animal.Gorilla -> R.drawable.wrapped_gorilla
-                  WrappedSlide.Caffeine.Animal.Lion -> R.drawable.wrapped_lion
-                  WrappedSlide.Caffeine.Animal.Bear -> R.drawable.wrapped_bear
-                  WrappedSlide.Caffeine.Animal.Moose -> R.drawable.wrapped_moose
-                  else -> R.drawable.wrapped_coffee_beans
-                }
-              )
-
-              Image(
-                painter,
-                contentDescription = null,
-                contentScale = ContentScale.Fit,
-                modifier = Modifier.size(72.dp)
-              )
-            }
-          )
-
-        is WrappedSlide.MostActive ->
-          ListItem(
-            headlineContent = {
-              Text(
-                "You were most active on ${
-                  slide.weekday.getDisplayName(TextStyle.FULL, locale)
-                }s at ${slide.hour} o'clock."
-              )
-            },
-            leadingContent = {
-              val painter = painterResource(R.drawable.wrapped_clock)
-
-              Image(
-                painter,
-                contentDescription = null,
-                contentScale = ContentScale.Fit,
-                modifier = Modifier.size(72.dp)
-              )
-            }
-          )
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedSlide.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedSlide.kt
deleted file mode 100644
index ec264b6d77d268b361ef974abb7aff4af74c22a9..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedSlide.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.home.wrapped
-
-import de.chaosdorf.mete.model.DrinkId
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.model.Transaction
-import kotlinx.datetime.DayOfWeek
-import kotlinx.datetime.TimeZone
-import kotlinx.datetime.toLocalDateTime
-
-sealed class WrappedSlide {
-  interface Factory {
-    fun create(
-      transactions: List<Transaction>,
-      drinks: Map<DrinkId, Drink>
-    ): WrappedSlide?
-  }
-
-  data class MostBoughtDrink(
-    val drink: Drink,
-    val count: Int,
-  ) : WrappedSlide() {
-    companion object : Factory {
-      override fun create(
-        transactions: List<Transaction>,
-        drinks: Map<DrinkId, Drink>
-      ): WrappedSlide? = transactions
-        .mapNotNull { drinks[it.drinkId] }
-        .groupingBy { it }
-        .eachCount()
-        .maxByOrNull { it.value }
-        ?.let { (mostBoughtDrink, count) ->
-          MostBoughtDrink(mostBoughtDrink, count)
-        }
-    }
-  }
-
-  data class Caffeine(
-    val total: Double,
-    val wouldKill: Animal?,
-  ) : WrappedSlide() {
-    enum class Animal(val lethalDosage: Double) {
-      Hamster(0.25),
-      Squirrel(0.3),
-      Rat(0.4),
-      GuineaPig(0.9),
-      Lemur(2.5),
-      Cat(5.0),
-      Koala(9.0),
-      Coyote(13.0),
-      Lynx(23.0),
-      Capybara(55.0),
-      Jaguar(81.0),
-      Reindeer(101.0),
-      Gorilla(140.0),
-      Lion(175.0),
-      Bear(278.0),
-      Moose(368.0),
-      Bison(540.0)
-    }
-
-    companion object : Factory {
-      override fun create(
-        transactions: List<Transaction>,
-        drinks: Map<DrinkId, Drink>
-      ): WrappedSlide = transactions
-        .mapNotNull { drinks[it.drinkId] }
-        .mapNotNull { drink -> drink.caffeine?.let { it * drink.volume.toDouble() * 10 } }
-        .sum()
-        .let { dosage ->
-          Caffeine(
-            dosage,
-            Animal.entries
-              .sortedBy(Animal::lethalDosage)
-              .lastOrNull { it.lethalDosage < dosage }
-          )
-        }
-    }
-  }
-
-  data class MostActive(
-    val weekday: DayOfWeek,
-    val hour: Int,
-  ) : WrappedSlide() {
-    companion object : Factory {
-      override fun create(
-        transactions: List<Transaction>,
-        drinks: Map<DrinkId, Drink>
-      ): WrappedSlide? = transactions
-        .map { it.timestamp.toLocalDateTime(TimeZone.currentSystemDefault()) }
-        .groupingBy { Pair(it.dayOfWeek, it.hour) }
-        .eachCount()
-        .maxByOrNull { it.value }
-        ?.key
-        ?.let { (dayOfWeek, hour) ->
-          MostActive(dayOfWeek, hour)
-        }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedViewModel.kt
deleted file mode 100644
index 91e1f7d4666784c7874a3100c5ddfb4e82634769..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/wrapped/WrappedViewModel.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.home.wrapped
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import de.chaosdorf.mete.model.DrinkId
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.Drink
-import de.chaosdorf.meteroid.model.DrinkRepository
-import de.chaosdorf.meteroid.model.Transaction
-import de.chaosdorf.meteroid.model.TransactionRepository
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.datetime.*
-import javax.inject.Inject
-
-@HiltViewModel
-class WrappedViewModel @Inject constructor(
-  savedStateHandle: SavedStateHandle,
-  repository: TransactionRepository,
-  drinkRepository: DrinkRepository,
-) : ViewModel() {
-  private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
-  private val userId = UserId(checkNotNull(savedStateHandle["user"]))
-
-  private fun List<Transaction>.filterAudits(year: Int): List<Transaction> {
-    val yearBegin = LocalDateTime(year, Month.JANUARY, 1, 0, 0, 0)
-      .toInstant(TimeZone.UTC)
-    val yearEnd = LocalDateTime(year, Month.DECEMBER, 31, 23, 59, 59)
-      .toInstant(TimeZone.UTC)
-    return this.filter {
-      it.timestamp in yearBegin..yearEnd
-    }
-  }
-
-  val slides: StateFlow<List<WrappedSlide>> = combine(
-    repository.getAllFlow(serverId, userId),
-    drinkRepository.getAllFlow(serverId)
-  ) { transactions, drinks ->
-    val drinkMap: Map<DrinkId, Drink> = drinks.associateBy(Drink::drinkId)
-    val factories = listOf(
-      WrappedSlide.MostBoughtDrink,
-      WrappedSlide.Caffeine,
-      WrappedSlide.MostActive
-    )
-    val now = Clock.System.now().toLocalDateTime(TimeZone.UTC)
-    val content = transactions.filterAudits(now.year)
-    factories.mapNotNull { it.create(content, drinkMap) }
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavigation.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavigation.kt
deleted file mode 100644
index 267caa347782514dbda46843504bb5ccb95d4bc9..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidNavigation.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.activity.compose.BackHandler
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import androidx.navigation.compose.currentBackStackEntryAsState
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.sync.base.SyncHandler
-import de.chaosdorf.meteroid.ui.MeteroidScreen
-import de.chaosdorf.meteroid.util.findStartDestination
-
-@Composable
-fun MeteroidNavigation(navController: NavController, viewModel: NavigationViewModel) {
-  val backStackEntry by navController.currentBackStackEntryAsState()
-  val activeRoute = MeteroidScreen.byRoute(backStackEntry?.destination?.route)
-
-  val expanded by viewModel.expanded.collectAsState()
-  val account by viewModel.account.collectAsState()
-  val entries by viewModel.entries.collectAsState()
-  val syncState by viewModel.syncState.collectAsState()
-
-  LaunchedEffect(navController) {
-    navController.addOnDestinationChangedListener { _, _, arguments ->
-      val serverId = arguments?.getLong("server")?.let(::ServerId)
-      val userId = arguments?.getLong("user")?.let(::UserId)
-
-      viewModel.account.value = AccountPreferences.State(serverId, userId)
-      viewModel.expanded.value = false
-    }
-  }
-
-  BackHandler(expanded) {
-    viewModel.expanded.value = false
-  }
-
-  val verticalContentPadding: Dp by animateDpAsState(if (expanded) 4.dp else 0.dp, label = "verticalContentPadding")
-  val shadowElevation: Dp by animateDpAsState(if (expanded) 16.dp else 4.dp, label = "shadowElevation")
-
-  Surface(
-    Modifier.fillMaxWidth().animateContentSize()
-      .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 128.dp),
-    shape = RoundedCornerShape(32.dp),
-    shadowElevation = shadowElevation,
-    tonalElevation = 8.dp
-  ) {
-    Box {
-      LazyColumn(contentPadding = PaddingValues(vertical = verticalContentPadding)) {
-        items(entries, key = NavigationElement::key) { entry ->
-          val isCurrent = entry.isCurrent(activeRoute, account?.server, account?.user)
-
-          NavigationAnimationContainer(
-            expanded || isCurrent
-          ) {
-            when (entry) {
-              is NavigationElement.ServerElement -> NavigationServerItem(expanded, entry.server) {
-                viewModel.expanded.value = true
-              }
-
-              is NavigationElement.UserElement -> NavigationUserItem(expanded,
-                entry.user,
-                entry.pinned,
-                viewModel::togglePin,
-                onExpand = { viewModel.expanded.value = true }
-              ) {
-                if (isCurrent) {
-                  viewModel.expanded.value = false
-                } else {
-                  navController.navigate(
-                    MeteroidScreen.Home.Purchase.build(
-                      entry.user.serverId, entry.user.userId
-                    )
-                  ) {
-                    launchSingleTop = true
-                    restoreState = false
-                    popUpTo(findStartDestination(navController.graph).id) {
-                      saveState = false
-                    }
-                  }
-                  viewModel.selectUser(entry.user.serverId, entry.user.userId)
-                }
-              }
-
-              is NavigationElement.UserListElement -> NavigationUserListItem {
-                if (NavigationElement.ServerElement(entry.server)
-                    .isCurrent(activeRoute, account?.server, account?.user)
-                ) {
-                  viewModel.expanded.value = false
-                } else {
-                  navController.navigate(MeteroidScreen.UserList.build(entry.server.serverId)) {
-                    launchSingleTop = true
-                    restoreState = false
-                    popUpTo(findStartDestination(navController.graph).id) {
-                      saveState = false
-                    }
-                  }
-                }
-              }
-
-              NavigationElement.AddServerElement -> NavigationAddServerItem(expanded,
-                onExpand = { viewModel.expanded.value = true }
-              ) {
-                if (isCurrent) {
-                  viewModel.expanded.value = false
-                } else {
-                  navController.navigate(MeteroidScreen.AddServer.build())
-                }
-              }
-
-              NavigationElement.SettingsElement -> NavigationSettingsItem(
-                expanded,
-                onExpand = { viewModel.expanded.value = true }
-              ) {
-                if (isCurrent) {
-                  viewModel.expanded.value = false
-                } else {
-                  navController.navigate(MeteroidScreen.Settings.build())
-                }
-              }
-            }
-          }
-        }
-      }
-      AnimatedVisibility(syncState == SyncHandler.State.Loading, enter = fadeIn(), exit = fadeOut()) {
-        LinearProgressIndicator(Modifier.align(Alignment.TopCenter).requiredHeight(2.dp).fillMaxWidth())
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationElement.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationElement.kt
deleted file mode 100644
index 706d29924f2debd4e8084e0e7b472e443862a519..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationElement.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.Server
-import de.chaosdorf.meteroid.model.User
-import de.chaosdorf.meteroid.ui.MeteroidScreen
-
-sealed class NavigationElement(val key: String) {
-  abstract fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean
-
-  data class ServerElement(
-    val server: Server,
-  ) : NavigationElement("navigation-${server.serverId}") {
-
-    override fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean {
-      return route == MeteroidScreen.UserList && serverId == server.serverId
-    }
-  }
-
-  data class UserElement(
-    val user: User,
-    val pinned: Boolean,
-  ) : NavigationElement("navigation-${user.serverId}-${user.userId}-${if (pinned) "pinned" else "current"}") {
-    override fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean {
-      return route is MeteroidScreen.Home && serverId == user.serverId && userId == user.userId
-    }
-  }
-
-  data class UserListElement(
-    val server: Server,
-  ) : NavigationElement("navigation-${server.serverId}-userList") {
-    override fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean = false
-  }
-
-  data object AddServerElement : NavigationElement("navigation-addServer") {
-
-    override fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean {
-      return route == MeteroidScreen.AddServer
-    }
-  }
-
-  data object SettingsElement : NavigationElement("navigation-settings") {
-
-    override fun isCurrent(route: MeteroidScreen?, serverId: ServerId?, userId: UserId?): Boolean {
-      return route == MeteroidScreen.Settings
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt
deleted file mode 100644
index 2092a71aa639e57025d30dc9c5293e6cc2c1709a..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.clickable
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import de.chaosdorf.meteroid.model.Server
-import de.chaosdorf.meteroid.ui.ServerAvatar
-import de.chaosdorf.meteroid.util.humanReadableHost
-import okhttp3.HttpUrl.Companion.toHttpUrl
-
-@Composable
-fun NavigationServerItem(expanded: Boolean, server: Server, onExpand: () -> Unit) {
-  val host = humanReadableHost(server.url.toHttpUrl())
-
-  ListItem(
-    headlineContent = { Text(server.name ?: host, maxLines = 1, overflow = TextOverflow.Ellipsis) },
-    supportingContent = { if (server.name != null) Text(host, maxLines = 1, overflow = TextOverflow.Ellipsis) },
-    leadingContent = {
-      ServerAvatar(server.logoUrl)
-    },
-    modifier = Modifier.requiredHeight(64.dp).let {
-      if (expanded) it
-      else it.clickable(onClick = onExpand)
-    }
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt
deleted file mode 100644
index b42ba6160e862de008cb734b93d10334d5e57c0a..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.animation.AnimatedVisibility
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Star
-import androidx.compose.material.icons.filled.StarOutline
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.User
-import de.chaosdorf.meteroid.ui.UserAvatar
-import de.chaosdorf.meteroid.ui.home.PriceBadge
-
-@Composable
-fun NavigationUserItem(
-  expanded: Boolean,
-  user: User,
-  pinned: Boolean,
-  onTogglePin: (ServerId, UserId) -> Unit,
-  onExpand: () -> Unit,
-  onClick: () -> Unit,
-) {
-  ListItem(
-    headlineContent = { Text(user.name, maxLines = 1, overflow = TextOverflow.Ellipsis) },
-    supportingContent = { if (user.email != null) Text(user.email!!, maxLines = 1, overflow = TextOverflow.Ellipsis) },
-    leadingContent = { UserAvatar(user.gravatarUrl) },
-    trailingContent = {
-      Row(verticalAlignment = Alignment.CenterVertically) {
-        AnimatedVisibility(!expanded, enter = fadeIn(), exit = fadeOut()) {
-          IconButton(onClick = { onTogglePin(user.serverId, user.userId) }) {
-            Icon(
-              if (pinned) Icons.Default.Star else Icons.Default.StarOutline,
-              contentDescription = null
-            )
-          }
-        }
-        PriceBadge(user.balance)
-      }
-    },
-    modifier = Modifier.requiredHeight(64.dp)
-      .clickable(onClick = if (expanded) onClick else onExpand)
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt
deleted file mode 100644
index 9a89652dd3453ed5fdf2916c0568214cdbf12494..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Group
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import de.chaosdorf.meteroid.ui.AvatarLayout
-
-@Composable
-fun NavigationUserListItem(onClick: () -> Unit) {
-  Column {
-    ListItem(
-      headlineContent = { Text("All Users") },
-      leadingContent = {
-        AvatarLayout {
-          Icon(Icons.Default.Group, contentDescription = null)
-        }
-      },
-      modifier = Modifier.requiredHeight(48.dp).clickable(onClick = onClick)
-    )
-    HorizontalDivider()
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationViewModel.kt
deleted file mode 100644
index e94f2be97aefa1a7ef00bb8e0afb42a19f492b1e..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationViewModel.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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 android.util.Log
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.*
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import de.chaosdorf.meteroid.sync.AccountProvider
-import de.chaosdorf.meteroid.sync.SyncManager
-import de.chaosdorf.meteroid.sync.base.SyncHandler
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class NavigationViewModel @Inject constructor(
-  serverRepository: ServerRepository,
-  userRepository: UserRepository,
-  pinnedUserRepository: PinnedUserRepository,
-  syncManager: SyncManager,
-  private val accountProvider: AccountProvider,
-  private val preferences: AccountPreferences
-) : ViewModel() {
-  val expanded = MutableStateFlow(false)
-  val account = MutableStateFlow<AccountPreferences.State?>(null)
-
-  val syncState = syncManager.syncState
-    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), SyncHandler.State.Idle)
-
-  private val servers: StateFlow<List<Server>> = serverRepository.getAllFlow()
-    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  private val pinnedUsers: StateFlow<List<User>> = pinnedUserRepository.getPinnedUsersFlow()
-    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  private val currentUser: StateFlow<User?> = account.flatMapLatest { account ->
-    if (account?.server == null || account.user == null) flowOf(null)
-    else userRepository.getFlow(account.server, account.user)
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
-
-  val historyDisabled = currentUser.map {
-    it?.audit == false
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
-
-  val entries: StateFlow<List<NavigationElement>> = combine(
-    servers,
-    pinnedUsers,
-    currentUser,
-  ) { servers, pinnedUsers, currentUser ->
-    val entries = mutableListOf<NavigationElement>()
-    val userInList = currentUser != null && pinnedUsers.any {
-      it.serverId == currentUser.serverId && it.userId == currentUser.userId
-    }
-
-    for (server in servers) {
-      entries.add(NavigationElement.ServerElement(server))
-      if (currentUser != null && currentUser.serverId == server.serverId && !userInList) {
-        entries.add(NavigationElement.UserElement(currentUser, pinned = false))
-      }
-      for (user in pinnedUsers) {
-        if (user.serverId == server.serverId) {
-          entries.add(NavigationElement.UserElement(user, pinned = true))
-        }
-      }
-      entries.add(NavigationElement.UserListElement(server))
-    }
-
-    entries.add(NavigationElement.AddServerElement)
-    entries.add(NavigationElement.SettingsElement)
-
-    entries
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  init {
-    viewModelScope.launch {
-      account.collectLatest { account ->
-        val serverId = account?.server
-        val userId = account?.user
-        if (serverId != null && userId != null) {
-          val server = serverRepository.get(serverId)
-          val user = userRepository.get(serverId, userId)
-          if (server != null && user != null) {
-            syncManager.sync(server, user, incremental = true)
-          }
-        }
-      }
-    }
-  }
-
-  fun togglePin(serverId: ServerId, userId: UserId) {
-    viewModelScope.launch {
-      accountProvider.togglePin(serverId, userId)
-    }
-  }
-
-  fun selectUser(serverId: ServerId, userId: UserId) {
-    Log.i("UserListViewModel", "Updating AccountPreferences: $serverId $userId")
-    viewModelScope.launch {
-      preferences.setUser(serverId, userId)
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerScreen.kt
deleted file mode 100644
index 46489c8cbcdb76944d1ade062eb46b381ba3cf13..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerScreen.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.servers
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import coil3.compose.AsyncImage
-import kotlinx.coroutines.launch
-import okhttp3.HttpUrl.Companion.toHttpUrl
-
-@Composable
-fun AddServerScreen(
-  navController: NavController,
-  viewModel: AddServerViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
-) {
-  val scope = rememberCoroutineScope()
-  val url by viewModel.url.collectAsState()
-  val server by viewModel.server.collectAsState()
-
-  Column(
-    Modifier
-      .padding(contentPadding)
-      .padding(16.dp, 8.dp)
-  ) {
-    TextField(
-      label = { Text("Server URL") },
-      value = url,
-      onValueChange = { viewModel.url.value = it },
-      modifier = Modifier.fillMaxWidth()
-    )
-
-    server?.let { server ->
-      Card(
-        modifier = Modifier.padding(vertical = 8.dp)
-      ) {
-        Row(modifier = Modifier.padding(16.dp, 8.dp)) {
-          AsyncImage(
-            server.logoUrl,
-            contentDescription = null,
-            contentScale = ContentScale.Crop,
-            modifier = Modifier.size(48.dp)
-          )
-          Spacer(Modifier.width(16.dp))
-          Column(modifier = Modifier.align(Alignment.CenterVertically)) {
-            Text(
-              server.name!!,
-              fontWeight = FontWeight.SemiBold,
-              style = MaterialTheme.typography.bodyMedium
-            )
-            Text(
-              server.url.toHttpUrl().host,
-              color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.67f),
-              fontWeight = FontWeight.Medium,
-              style = MaterialTheme.typography.bodyMedium
-            )
-          }
-
-          Spacer(
-            Modifier
-              .width(16.dp)
-              .weight(1.0f)
-          )
-
-          IconButton(onClick = {
-            scope.launch {
-              viewModel.addServer()
-              navController.navigateUp()
-            }
-          }) {
-            Icon(Icons.Default.Add, contentDescription = "Add Server")
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListItem.kt
deleted file mode 100644
index f903e304e573d6dcc7efd3d66ecdc569b96b7e9d..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListItem.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.userlist
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-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.unit.dp
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.User
-import de.chaosdorf.meteroid.util.rememberAvatarPainter
-
-@Composable
-fun UserListItem(
-  item: User,
-  onSelect: (ServerId, UserId) -> Unit = { _, _ -> }
-) {
-  val avatarPainter = rememberAvatarPainter(
-    item.gravatarUrl,
-    32.dp, 32.dp,
-    MaterialTheme.colorScheme.secondary
-  )
-
-  ListItem(
-    headlineContent = { Text(item.name) },
-    supportingContent = {
-      item.email?.let { email ->
-        Text(email)
-      }
-    },
-    leadingContent = {
-      Box(
-        modifier = Modifier
-          .size(48.dp)
-          .clip(CircleShape)
-          .background(MaterialTheme.colorScheme.secondaryContainer)
-      ) {
-        Image(
-          avatarPainter,
-          contentDescription = null,
-          contentScale = ContentScale.Crop,
-          modifier = Modifier.align(Alignment.Center)
-        )
-      }
-    },
-    modifier = Modifier.clickable {
-      onSelect(item.serverId, item.userId)
-    }
-  )
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListScreen.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListScreen.kt
deleted file mode 100644
index a7e6156d8a2b3f83c85579e5dd6530617c19449d..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListScreen.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.userlist
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-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.ListItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-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 androidx.navigation.NavController
-import de.chaosdorf.meteroid.ui.MeteroidScreen
-import de.chaosdorf.meteroid.util.findStartDestination
-
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun UserListScreen(
-  navController: NavController,
-  viewModel: UserListViewModel,
-  contentPadding: PaddingValues = PaddingValues(),
-) {
-  val users by viewModel.users.collectAsState()
-  val pinnedUsers by viewModel.pinnedUsers.collectAsState()
-
-  LazyColumn(contentPadding = contentPadding) {
-    if (pinnedUsers.isNotEmpty()) {
-      stickyHeader("pinned") {
-        ListItem(headlineContent = {
-          Text(
-            "Pinned",
-            style = MaterialTheme.typography.labelMedium,
-            modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 16.dp, bottom = 4.dp)
-          )
-        })
-      }
-
-      items(
-        pinnedUsers,
-        key = { "pinned-${it.serverId}-${it.userId}" },
-      ) { user ->
-        UserListItem(user) { serverId, userId ->
-          navController.navigate(MeteroidScreen.Home.Purchase.build(serverId, userId)) {
-            launchSingleTop = true
-            restoreState = false
-            popUpTo(findStartDestination(navController.graph).id) {
-              saveState = false
-            }
-          }
-          viewModel.selectUser(serverId, userId)
-        }
-      }
-    }
-
-    for (character in 'A'..'Z') {
-      val group = users.filter { it.name.startsWith(character, ignoreCase = true) }
-      if (group.isNotEmpty()) {
-        stickyHeader(character.toString()) {
-          ListItem(headlineContent = {
-            Text(
-              "$character",
-              style = MaterialTheme.typography.labelMedium,
-              modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 16.dp, bottom = 4.dp)
-            )
-          })
-        }
-
-        items(
-          group,
-          key = { "user-${it.serverId}-${it.userId}" },
-        ) { user ->
-          UserListItem(user) { serverId, userId ->
-            navController.navigate(MeteroidScreen.Home.Purchase.build(serverId, userId)) {
-              launchSingleTop = true
-              restoreState = false
-              popUpTo(findStartDestination(navController.graph).id) {
-                saveState = false
-              }
-            }
-            viewModel.selectUser(serverId, userId)
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListViewModel.kt
deleted file mode 100644
index e50d80dd4258292317e8ba0614a2be15906b339f..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/userlist/UserListViewModel.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.userlist
-
-import android.util.Log
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.model.PinnedUserRepository
-import de.chaosdorf.meteroid.model.User
-import de.chaosdorf.meteroid.model.UserRepository
-import de.chaosdorf.meteroid.storage.AccountPreferences
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class UserListViewModel @Inject constructor(
-  savedStateHandle: SavedStateHandle,
-  userRepository: UserRepository,
-  pinnedUserRepository: PinnedUserRepository,
-  private val preferences: AccountPreferences,
-) : ViewModel() {
-  private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
-
-  val users: StateFlow<List<User>> = userRepository.getAllFlow(serverId)
-    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  val pinnedUsers = combine(
-    userRepository.getAllFlow(serverId),
-    pinnedUserRepository.getAllFlow(serverId)
-  ) { users, pinned ->
-    users.filter { user -> pinned.contains(user.userId) }
-  }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
-
-  fun selectUser(serverId: ServerId, userId: UserId) {
-    Log.i("UserListViewModel", "Updating AccountPreferences: $serverId $userId")
-    viewModelScope.launch {
-      preferences.setUser(serverId, userId)
-    }
-  }
-}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/MeteroidViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/InitViewModel.kt
similarity index 66%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/MeteroidViewModel.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/InitViewModel.kt
index 96447ecd0d6a17823c30ffbdb625f61ddb727d00..2885bc48e49255b490324d6c74fb6d8bc7371a6c 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/MeteroidViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/InitViewModel.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,28 +22,36 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid
+package de.chaosdorf.meteroid.viewmodel
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.lifecycle.HiltViewModel
 import de.chaosdorf.meteroid.model.ServerRepository
-import de.chaosdorf.meteroid.storage.AccountPreferences
 import de.chaosdorf.meteroid.sync.SyncManager
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-@HiltViewModel
-class MeteroidViewModel @Inject constructor(
-  accountPreferences: AccountPreferences,
+@AssistedFactory
+interface InitViewModelFactory {
+  fun create(): InitViewModel
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@HiltViewModel(assistedFactory = InitViewModelFactory::class)
+class InitViewModel @AssistedInject constructor(
   serverRepository: ServerRepository,
-  syncManager: SyncManager
+  syncManager: SyncManager,
 ) : ViewModel() {
-  val initialAccount: StateFlow<AccountPreferences.State?> = accountPreferences.state
-    .filterNotNull()
-    .take(1)
-    .stateIn(viewModelScope, SharingStarted.Eagerly, null)
+  val setupComplete = serverRepository.getAllFlow()
+    .mapLatest { it.isNotEmpty() }
+    .stateIn(viewModelScope, WhileSubscribed(), null)
 
   init {
     viewModelScope.launch {
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/ServerListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/ServerListViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c42af5e3aca698448b245d0ffd6877e9454bef6e
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/ServerListViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.meteroid.model.ServerRepository
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+@AssistedFactory
+interface ServerListViewModelFactory {
+  fun create(): ServerListViewModel
+}
+
+@HiltViewModel(assistedFactory = ServerListViewModelFactory::class)
+class ServerListViewModel @AssistedInject constructor(
+  serverRepository: ServerRepository,
+) : ViewModel() {
+  val servers = serverRepository.getAllFlow()
+    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SettingsViewModel.kt
similarity index 71%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsViewModel.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SettingsViewModel.kt
index 35aa5a251c13ed8f7f1a279f723f1e305c10fa59..08110831a6d424b5f9801a7d8b6998b7488d8248 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SettingsViewModel.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,11 +22,20 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.settings
+package de.chaosdorf.meteroid.viewmodel
 
 import androidx.lifecycle.ViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@HiltViewModel
-class SettingsViewModel @Inject constructor() : ViewModel()
+@AssistedFactory
+interface SettingsViewModelFactory {
+  fun create(): SettingsViewModel
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@HiltViewModel(assistedFactory = SettingsViewModelFactory::class)
+class SettingsViewModel @AssistedInject constructor() : ViewModel() {
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SetupViewModel.kt
similarity index 54%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerViewModel.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SetupViewModel.kt
index 8c1ee10134223eea50d93c2b5c25433bb857215a..51bf79bc000900ee9cd02173bd08cf4b7db22145 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/servers/AddServerViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/SetupViewModel.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,38 +22,46 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.servers
+package de.chaosdorf.meteroid.viewmodel
 
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.lifecycle.HiltViewModel
 import de.chaosdorf.mete.model.MeteApiFactory
 import de.chaosdorf.mete.model.ServerId
 import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerRepository
+import de.chaosdorf.meteroid.sync.SyncManager
 import de.chaosdorf.meteroid.util.newServer
-import kotlinx.coroutines.flow.*
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.MutableStateFlow
 
-@HiltViewModel
-class AddServerViewModel @Inject constructor(
-  private val factory: MeteApiFactory,
-  private val repository: ServerRepository
-) : ViewModel() {
-  val url = MutableStateFlow("")
+@AssistedFactory
+interface SetupViewModelFactory {
+  fun create(): SetupViewModel
+}
 
-  val server: StateFlow<Server?> = url
-    .debounce(300.milliseconds)
-    .mapLatest { factory.newServer(ServerId(-1), it) }
-    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+@HiltViewModel(assistedFactory = SetupViewModelFactory::class)
+class SetupViewModel @AssistedInject constructor(
+  private val apiFactory: MeteApiFactory,
+  private val serverRepository: ServerRepository,
+  private val syncManager: SyncManager,
+) : ViewModel() {
+  val serverUrl = MutableStateFlow("http://192.168.1.41:8080")
+  val error = MutableStateFlow<String?>(null)
 
-  suspend fun addServer() {
-    val highestServerId = repository.getAll().maxOfOrNull { it.serverId.value } ?: 0
-    val serverId = ServerId(highestServerId + 1)
-    val server = factory.newServer(serverId, url.value)
-    if (server != null) {
-      repository.save(server)
-    }
+  suspend fun add(): Server? {
+      val highestServerId = serverRepository.getAll().maxOfOrNull { it.serverId.value } ?: 0
+      val serverId = ServerId(highestServerId + 1)
+      val server = apiFactory.newServer(serverId, serverUrl.value)
+      if (server != null) {
+        serverRepository.save(server)
+        syncManager.sync(server, null, incremental = false)
+        error.value = null
+        return server
+      } else {
+        error.value = "Server not found"
+        return null
+      }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserListViewModel.kt
similarity index 56%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositViewModel.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserListViewModel.kt
index 82ad5ee33f2b712ac83e63d2336e30d70b6dcb92..42bb4c4af67061a2afb14c32d2a7cd63a77df459 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/home/deposit/DepositViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserListViewModel.kt
@@ -1,7 +1,7 @@
 /*
  * The MIT License (MIT)
  *
- * Copyright (c) 2013-2023 Chaosdorf e.V.
+ * Copyright (c) 2013-2025 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
@@ -22,38 +22,34 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.ui.home.deposit
+package de.chaosdorf.meteroid.viewmodel
 
-import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.lifecycle.HiltViewModel
 import de.chaosdorf.mete.model.ServerId
-import de.chaosdorf.mete.model.UserId
-import de.chaosdorf.meteroid.sync.AccountProvider
-import de.chaosdorf.meteroid.sync.SyncManager
-import kotlinx.coroutines.launch
-import javax.inject.Inject
+import de.chaosdorf.meteroid.model.UserRepository
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 
-@HiltViewModel
-class DepositViewModel @Inject constructor(
-  savedStateHandle: SavedStateHandle,
-  private val accountProvider: AccountProvider,
-  private val syncManager: SyncManager,
-) : ViewModel() {
-  private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
-  private val userId = UserId(checkNotNull(savedStateHandle["user"]))
 
-  val money: List<MonetaryAmount> = MonetaryAmount.entries
+@AssistedFactory
+interface UserListViewModelFactory {
+  fun create(
+    @Assisted("serverId") serverId: Long,
+  ): UserListViewModel
+}
+
+@HiltViewModel(assistedFactory = UserListViewModelFactory::class)
+class UserListViewModel @AssistedInject constructor(
+  @Assisted("serverId") serverId: Long,
+  userRepository: UserRepository,
+) : ViewModel() {
+  val serverId: ServerId = ServerId(serverId)
 
-  fun deposit(item: MonetaryAmount, onBack: () -> Unit) {
-    viewModelScope.launch {
-      accountProvider.account(serverId, userId)?.let { account ->
-        syncManager.deposit(account, item.amount)
-        if (!account.pinned) {
-          onBack()
-        }
-      }
-    }
-  }
+  val users = userRepository.getAllFlow(this.serverId)
+    .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b813133f28798974deeca14f62fa47d0537284cc
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/UserViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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.viewmodel
+
+import androidx.lifecycle.ViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.mete.model.ServerId
+import de.chaosdorf.mete.model.UserId
+
+@AssistedFactory
+interface UserViewModelFactory {
+  fun create(
+    @Assisted("serverId") serverId: Long,
+    @Assisted("userId") userId: Long,
+  ): UserViewModel
+}
+
+@HiltViewModel(assistedFactory = UserViewModelFactory::class)
+class UserViewModel @AssistedInject constructor(
+  @Assisted("serverId") serverId: Long,
+  @Assisted("userId") userId: Long,
+) : ViewModel() {
+  val serverId: ServerId = ServerId(serverId)
+  val userId: UserId = UserId(userId)
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/WrappedViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/WrappedViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b1e18fa4a8ffd303b87e4fa125ffeef633656486
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/WrappedViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2025 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.viewmodel
+
+import androidx.lifecycle.ViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.lifecycle.HiltViewModel
+import de.chaosdorf.mete.model.ServerId
+import de.chaosdorf.mete.model.UserId
+
+@AssistedFactory
+interface WrappedViewModelFactory {
+  fun create(
+    @Assisted("serverId") serverId: Long,
+    @Assisted("userId") userId: Long,
+  ): WrappedViewModel
+}
+
+@HiltViewModel(assistedFactory = WrappedViewModelFactory::class)
+class WrappedViewModel @AssistedInject constructor(
+  @Assisted("serverId") serverId: Long,
+  @Assisted("userId") userId: Long,
+) : ViewModel() {
+  val serverId: ServerId = ServerId(serverId)
+  val userId: UserId = UserId(userId)
+}
diff --git a/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
index 3f2dd4766555d09763af9539e3f5bb4196bb4c77..713956bb8537da70928368d8f166485b507d1700 100644
--- a/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
+++ b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt
@@ -16,11 +16,11 @@ class AndroidApplicationConvention : Plugin<Project> {
       }
 
       extensions.configure<ApplicationExtension> {
-        compileSdk = 35
+        compileSdk = 36
 
         defaultConfig {
           minSdk = 21
-          targetSdk = 35
+          targetSdk = 36
 
           applicationId = "${rootProject.group}.${rootProject.name.lowercase(Locale.ROOT)}"
           versionCode = cmd("git", "rev-list", "--count", "HEAD")?.toIntOrNull() ?: 1
diff --git a/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
index 0dd138372be5df5199f69d2e499218006ced97d8..8086c78cecbbf99cedda4146effebb821398ab6e 100644
--- a/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
+++ b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt
@@ -12,7 +12,7 @@ class AndroidLibraryConvention : Plugin<Project> {
       }
 
       extensions.configure<LibraryExtension> {
-        compileSdk = 35
+        compileSdk = 36
 
         defaultConfig {
           minSdk = 21
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a32d359245708d94754ac60569a3a0586be32a50..b82e2a669f1651ca76da16927cbc3c8c7384c53f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,8 @@ androidx-appcompat = "1.7.1"
 androidx-compose = "1.8.2"
 androidx-compose-compiler = "1.5.15"
 androidx-datastore = "1.1.7"
-androidx-navigation = "2.9.0"
+androidx-navigation3 = "1.0.0-alpha03"
+androidx-navigation3-viewmodel = "1.0.0-alpha01"
 androidx-room = "2.7.1"
 androidx-splashscreen = "1.0.1"
 androidx-material3 = "1.3.2"
@@ -62,7 +63,9 @@ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
 retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
 retrofit-converter-kotlinx = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
 
-androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
+androidx-navigation3-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-navigation3-viewmodel" }
+androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "androidx-navigation3" }
+androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "androidx-navigation3" }
 
 junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
 junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" }