Skip to content
Snippets Groups Projects
Unverified Commit db1d2d74 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

feat: redesigned navigation

parent 7df3b85b
No related branches found
No related tags found
No related merge requests found
Showing
with 632 additions and 90 deletions
/*
* 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.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
}
) {}
}
}
/*
* 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)
}
)
}
/*
* 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.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.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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
@Composable
fun NavigationSettingsItem(expanded: Boolean, onExpand: () -> Unit, onClick: () -> Unit) {
val height: Dp by animateDpAsState(if (expanded) 48.dp else 64.dp, label = "height")
ListItem(
headlineContent = { Text("Settings") },
leadingContent = {
AvatarLayout {
Icon(Icons.Filled.Settings, contentDescription = null)
}
},
modifier = Modifier.requiredHeight(height)
.clickable(onClick = if (expanded) onClick else onExpand)
)
}
...@@ -22,60 +22,59 @@ ...@@ -22,60 +22,59 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.servers 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.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons
import androidx.compose.foundation.lazy.items 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.ListItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import de.chaosdorf.mete.model.ServerId
import coil.compose.AsyncImage import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.ui.navigation.Routes import de.chaosdorf.meteroid.model.User
import okhttp3.HttpUrl.Companion.toHttpUrl import de.chaosdorf.meteroid.ui.UserAvatar
import de.chaosdorf.meteroid.ui.home.PriceBadge
@Composable @Composable
fun ServerListScreen( fun NavigationUserItem(
navController: NavController, expanded: Boolean,
viewModel: ServerListViewModel, user: User,
contentPadding: PaddingValues = PaddingValues(), pinned: Boolean,
onTogglePin: (ServerId, UserId) -> Unit,
onExpand: () -> Unit,
onClick: () -> Unit,
) { ) {
val servers by viewModel.servers.collectAsState()
LazyColumn(contentPadding = contentPadding) {
items(servers) { server ->
ListItem( ListItem(
headlineContent = { Text(server.name ?: server.url) }, headlineContent = { Text(user.name, maxLines = 1, overflow = TextOverflow.Ellipsis) },
supportingContent = { if (server.name != null) Text(server.url.toHttpUrl().host) }, supportingContent = { if (user.email != null) Text(user.email!!, maxLines = 1, overflow = TextOverflow.Ellipsis) },
leadingContent = { leadingContent = { UserAvatar(user.gravatarUrl) },
AsyncImage( trailingContent = {
server.logoUrl, Row(verticalAlignment = Alignment.CenterVertically) {
contentDescription = null, AnimatedVisibility(!expanded, enter = fadeIn(), exit = fadeOut()) {
contentScale = ContentScale.Crop, IconButton(onClick = { onTogglePin(user.serverId, user.userId) }) {
modifier = Modifier.size(48.dp) Icon(
if (pinned) Icons.Default.Star else Icons.Default.StarOutline,
contentDescription = null
) )
},
modifier = Modifier.clickable {
navController.navigate(Routes.Users.list(server.serverId))
viewModel.selectServer(server.serverId)
} }
)
} }
item { PriceBadge(user.balance)
ListItem(
headlineContent = { Text("Add Server") },
modifier = Modifier.clickable {
navController.navigate(Routes.Servers.Add)
} }
},
modifier = Modifier.requiredHeight(64.dp)
.clickable(onClick = if (expanded) onClick else onExpand)
) )
} }
}
}
/*
* 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()
}
}
...@@ -22,72 +22,95 @@ ...@@ -22,72 +22,95 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui package de.chaosdorf.meteroid.ui.navigation
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.PinnedUserRepository import de.chaosdorf.meteroid.model.*
import de.chaosdorf.meteroid.model.ServerId import de.chaosdorf.meteroid.storage.AccountPreferences
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.UserRepository
import de.chaosdorf.meteroid.sync.AccountProvider import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncManager import de.chaosdorf.meteroid.sync.SyncManager
import kotlinx.coroutines.flow.MutableStateFlow import de.chaosdorf.meteroid.sync.base.SyncHandler
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class NavigationViewModel @Inject constructor( class NavigationViewModel @Inject constructor(
syncManager: SyncManager,
serverRepository: ServerRepository, serverRepository: ServerRepository,
userRepository: UserRepository, userRepository: UserRepository,
pinnedUserRepository: PinnedUserRepository, pinnedUserRepository: PinnedUserRepository,
syncManager: SyncManager,
private val accountProvider: AccountProvider private val accountProvider: AccountProvider
) : ViewModel() { ) : ViewModel() {
val serverId = MutableStateFlow<ServerId?>(null) val expanded = MutableStateFlow(false)
val userId = MutableStateFlow<UserId?>(null) val account = MutableStateFlow<AccountPreferences.State?>(null)
val server = serverId.flatMapLatest { val syncState = syncManager.syncState
if (it == null) flowOf(null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), SyncHandler.State.Idle)
else serverRepository.getFlow(it)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val user = combine(serverId, userId) { serverId, userId -> private val servers: StateFlow<List<Server>> = serverRepository.getAllFlow()
if (serverId == null || userId == null) null .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
else Pair(serverId, userId)
}.flatMapLatest {
if (it == null) flowOf(null)
else userRepository.getFlow(it.first, it.second)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val pinned = combine(serverId, userId) { serverId, userId -> private val pinnedUsers: StateFlow<List<User>> = pinnedUserRepository.getPinnedUsersFlow()
if (serverId == null || userId == null) null .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
else Pair(serverId, userId)
}.flatMapLatest { private val currentUser: StateFlow<User?> = account.flatMapLatest { account ->
if (it == null) flowOf(null) if (account?.server == null || account.user == null) flowOf(null)
else pinnedUserRepository.isPinnedFlow(it.first, it.second) else userRepository.getFlow(account.server, account.user)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) }.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 { init {
viewModelScope.launch { viewModelScope.launch {
combine(server, user) { server, user -> account.collectLatest { account ->
server?.let { Pair(server, user) } val serverId = account?.server
}.distinctUntilChanged().collectLatest { account -> val userId = account?.user
account?.let { (server, 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) syncManager.sync(server, user, incremental = true)
} }
} }
} }
} }
}
fun togglePin(serverId: ServerId, userId: UserId) { fun togglePin(serverId: ServerId, userId: UserId) {
viewModelScope.launch { viewModelScope.launch {
......
...@@ -24,22 +24,10 @@ ...@@ -24,22 +24,10 @@
package de.chaosdorf.meteroid.ui.servers package de.chaosdorf.meteroid.ui.servers
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Card import androidx.compose.material3.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
...@@ -51,7 +39,6 @@ import androidx.compose.ui.text.font.FontWeight ...@@ -51,7 +39,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import de.chaosdorf.meteroid.ui.navigation.Routes
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
...@@ -112,7 +99,7 @@ fun AddServerScreen( ...@@ -112,7 +99,7 @@ fun AddServerScreen(
IconButton(onClick = { IconButton(onClick = {
scope.launch { scope.launch {
viewModel.addServer() viewModel.addServer()
navController.navigate(Routes.Servers.List) navController.navigateUp()
} }
}) { }) {
Icon(Icons.Default.Add, contentDescription = "Add Server") Icon(Icons.Default.Add, contentDescription = "Add Server")
......
...@@ -28,16 +28,11 @@ import androidx.lifecycle.ViewModel ...@@ -28,16 +28,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.ServerRepository import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.util.newServer import de.chaosdorf.meteroid.util.newServer
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
......
/*
* 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.settings
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.compose.ui.unit.dp
import androidx.navigation.NavController
@Composable
fun SettingsScreen(navController: NavController, viewModel: SettingsViewModel, contentPadding: PaddingValues) {
Column(
Modifier
.padding(contentPadding)
.padding(16.dp, 8.dp)
) {
Text("Settings")
}
}
...@@ -22,14 +22,11 @@ ...@@ -22,14 +22,11 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.sync package de.chaosdorf.meteroid.ui.settings
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SyncViewModel @Inject constructor( class SettingsViewModel @Inject constructor() : ViewModel()
) : ViewModel() {
}
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.users package de.chaosdorf.meteroid.ui.userlist
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
...@@ -39,8 +39,8 @@ import androidx.compose.ui.Modifier ...@@ -39,8 +39,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.util.rememberAvatarPainter import de.chaosdorf.meteroid.util.rememberAvatarPainter
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.users package de.chaosdorf.meteroid.ui.userlist
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
...@@ -38,7 +38,8 @@ import androidx.compose.runtime.getValue ...@@ -38,7 +38,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import de.chaosdorf.meteroid.ui.navigation.Routes import de.chaosdorf.meteroid.ui.MeteroidScreen
import de.chaosdorf.meteroid.util.findStartDestination
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
...@@ -64,10 +65,16 @@ fun UserListScreen( ...@@ -64,10 +65,16 @@ fun UserListScreen(
items( items(
pinnedUsers, pinnedUsers,
key = { "pinned-${it.userId}" }, key = { "pinned-${it.serverId}-${it.userId}" },
) { user -> ) { user ->
UserListItem(user) { serverId, userId -> UserListItem(user) { serverId, userId ->
navController.navigate(Routes.Home.purchase(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) viewModel.selectUser(serverId, userId)
} }
} }
...@@ -88,10 +95,16 @@ fun UserListScreen( ...@@ -88,10 +95,16 @@ fun UserListScreen(
items( items(
group, group,
key = { "${it.serverId}-${it.userId}" }, key = { "user-${it.serverId}-${it.userId}" },
) { user -> ) { user ->
UserListItem(user) { serverId, userId -> UserListItem(user) { serverId, userId ->
navController.navigate(Routes.Home.purchase(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) viewModel.selectUser(serverId, userId)
} }
} }
......
...@@ -22,16 +22,16 @@ ...@@ -22,16 +22,16 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.users package de.chaosdorf.meteroid.ui.userlist
import android.util.Log import android.util.Log
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.PinnedUserRepository import de.chaosdorf.meteroid.model.PinnedUserRepository
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.model.UserRepository import de.chaosdorf.meteroid.model.UserRepository
import de.chaosdorf.meteroid.storage.AccountPreferences import de.chaosdorf.meteroid.storage.AccountPreferences
......
/*
* 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.util
import android.os.Bundle
internal fun Bundle.toMap(): Map<String, Any?> =
keySet().associate { @Suppress("DEPRECATION") Pair(it, get(it)) }
...@@ -22,34 +22,14 @@ ...@@ -22,34 +22,14 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.sample package de.chaosdorf.meteroid.util
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import okhttp3.HttpUrl
import de.chaosdorf.mete.model.DrinkId
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.ServerId
class SampleDrinkProvider : PreviewParameterProvider<Drink> { fun humanReadableHost(url: HttpUrl): String {
override val values = sequenceOf( val actualPort = url.port
Drink( val defaultPort = HttpUrl.defaultPort(url.scheme)
serverId = ServerId(-1), val actualHost = url.host
drinkId = DrinkId(27), if (actualPort == defaultPort) return actualHost
active = true, return "$actualHost:$actualPort"
name = "Club Mate",
volume = 0.5.toBigDecimal(),
caffeine = null,
price = 1.5.toBigDecimal(),
logoUrl = "http://192.168.188.36:8080/system/drinks/logos/000/000/027/thumb/logo.png",
),
Drink(
serverId = ServerId(-1),
drinkId = DrinkId(15),
active = false,
name = "Paulaner Spezi",
volume = 0.5.toBigDecimal(),
caffeine = null,
price = 1.5.toBigDecimal(),
logoUrl = "http://192.168.188.36:8080/system/drinks/logos/000/000/015/thumb/logo.png",
)
)
} }
...@@ -25,8 +25,8 @@ ...@@ -25,8 +25,8 @@
package de.chaosdorf.meteroid.util package de.chaosdorf.meteroid.util
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import java.net.URI import java.net.URI
suspend fun MeteApiFactory.newServer(serverId: ServerId, baseUrl: String): Server? = try { suspend fun MeteApiFactory.newServer(serverId: ServerId, baseUrl: String): Server? = try {
......
/*
* 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.util
import androidx.navigation.NavBackStackEntry
internal fun NavBackStackEntry.toFancyString(): String {
val arguments = this.arguments?.toMap().orEmpty().toList()
.filter { (key, _) -> key == "server" || key == "user" }
.joinToString(", ", prefix = "(", postfix = ")") { (key, value) -> "$key=$value" }
return "${destination.route}$arguments"
}
internal fun Iterable<NavBackStackEntry>.toFancyString(): String = joinToString(
separator = " › ",
prefix = "[",
postfix = "]",
transform = NavBackStackEntry::toFancyString
)
/*
* 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.util
import androidx.navigation.NavDestination
import androidx.navigation.NavGraph
private val NavGraph.startDestination: NavDestination?
get() = findNode(startDestinationId)
tailrec fun findStartDestination(graph: NavDestination): NavDestination {
return if (graph is NavGraph) findStartDestination(graph.startDestination!!) else graph
}
...@@ -36,7 +36,8 @@ ...@@ -36,7 +36,8 @@
android:fillAlpha="0.5" android:fillAlpha="0.5"
android:fillColor="#000000" android:fillColor="#000000"
android:pathData="m21.7 60.9v11.1h38.8c0 0 0.256-0 0-0l-10.6-11.1z"/> android:pathData="m21.7 60.9v11.1h38.8c0 0 0.256-0 0-0l-10.6-11.1z"/>
<path android:pathData="m53.5 23.5-4.46 37.2c-0.136 1.14-3.2 1.6-13.3 1.6s-13.2-0.465-13.3-1.6l-4.38-39c14.3 4.73 19.3-2.75 35.4 1.86z"> <path
android:pathData="m53.5 23.5-4.46 37.2c-0.136 1.14-3.2 1.6-13.3 1.6s-13.2-0.465-13.3-1.6l-4.38-39c14.3 4.73 19.3-2.75 35.4 1.86z">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient <gradient
android:endX="0" android:endX="0"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment