Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • justJanne/meteroid
1 result
Select Git revision
Show changes
Showing
with 1381 additions and 63 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.drinks
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.sync.SyncHandler
import de.chaosdorf.meteroid.ui.navigation.HomeSections
import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
@Composable
fun DrinkListScreen(
viewModel: DrinkListViewModel,
onNavigate: (String) -> Unit = {}
) {
val account by viewModel.account.collectAsState()
val drinks by viewModel.drinks.collectAsState()
val syncState by viewModel.syncState.collectAsState()
Scaffold(
topBar = { MeteroidTopBar(account, onNavigate) },
bottomBar = {
MeteroidBottomBar(
currentRoute = HomeSections.PURCHASE,
historyEnabled = account?.second?.audit == true,
navigateTo = onNavigate
)
}
) { paddingValues: PaddingValues ->
Column {
if (syncState == SyncHandler.State.Loading) {
LinearProgressIndicator()
}
LazyVerticalGrid(
GridCells.Adaptive(120.dp),
modifier = Modifier.padding(paddingValues),
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
items(drinks) { drink ->
DrinkTile(drink)
}
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.drinks
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.DrinkSyncHandler
import de.chaosdorf.meteroid.sync.SyncHandler
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class DrinkListViewModel @Inject constructor(
accountProvider: AccountProvider,
repository: DrinkRepository,
syncHandler: DrinkSyncHandler
) : ViewModel() {
val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val drinks: StateFlow<List<Drink>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, _) ->
repository.getAllFlow(server.serverId)
} ?: flowOf(emptyList())
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val syncState: StateFlow<SyncHandler.State> = syncHandler.state
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.drinks
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.sample.SampleDrinkProvider
@Preview(widthDp = 120, showBackground = true)
@Composable
fun DrinkTile(
@PreviewParameter(SampleDrinkProvider::class) item: Drink
) {
val thumbPainter = rememberAsyncImagePainter(
item.logoUrl
)
val drinkPainter = rememberAsyncImagePainter(
item.logoUrl.replace("/thumb/", "/original/"),
error = thumbPainter
)
Column(
modifier = Modifier.padding(4.dp)
) {
Box {
Image(
drinkPainter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer)
.aspectRatio(1.0f)
.padding(8.dp)
)
Text(
String.format("%.02f €", item.price),
color = MaterialTheme.colorScheme.onPrimary,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(vertical = 12.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.primary)
.align(Alignment.BottomEnd)
.padding(horizontal = 8.dp)
)
}
Text(
item.name,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 4.dp)
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(horizontal = 4.dp)
.fillMaxWidth()
) {
Text(
String.format("%.02f l", item.volume),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
)
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.money
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.ui.navigation.HomeSections
import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
@Composable
fun MoneyListScreen(
viewModel: MoneyListViewModel,
onNavigate: (String) -> Unit = {}
) {
val account by viewModel.account.collectAsState()
Scaffold(
topBar = { MeteroidTopBar(account, onNavigate) },
bottomBar = {
MeteroidBottomBar(
currentRoute = HomeSections.DEPOSIT,
historyEnabled = account?.second?.audit == true,
navigateTo = onNavigate
)
}
) { paddingValues: PaddingValues ->
Column {
LazyVerticalGrid(
GridCells.Adaptive(120.dp),
modifier = Modifier.padding(paddingValues),
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
items(viewModel.money) { monetaryAmount ->
MoneyTile(monetaryAmount)
}
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.money
import androidx.annotation.DrawableRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.R
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.sync.AccountProvider
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
enum class MonetaryAmount(val amount: Double, @DrawableRes val image: Int) {
MONEY_50(0.50, R.drawable.euro_50),
MONEY_100(1.00, R.drawable.euro_100),
MONEY_200(2.00, R.drawable.euro_200),
MONEY_500(5.00, R.drawable.euro_500),
MONEY_1000(10.00, R.drawable.euro_1000),
MONEY_2000(20.00, R.drawable.euro_2000),
MONEY_5000(50.00, R.drawable.euro_5000),
}
@HiltViewModel
class MoneyListViewModel @Inject constructor(
accountProvider: AccountProvider
) : ViewModel() {
val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val money: List<MonetaryAmount> = MonetaryAmount.entries
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.money
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun MoneyTile(
item: MonetaryAmount
) {
Column(
modifier = Modifier.padding(4.dp)
) {
Box {
Image(
painterResource(item.image),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer)
.aspectRatio(1.0f)
.padding(8.dp)
)
Text(
String.format("%.02f €", item.amount),
color = MaterialTheme.colorScheme.onPrimary,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(vertical = 12.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.primary)
.align(Alignment.BottomEnd)
.padding(horizontal = 8.dp)
)
}
}
}
/*
* 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.material.icons.Icons
import androidx.compose.material.icons.twotone.History
import androidx.compose.material.icons.twotone.LocalAtm
import androidx.compose.ui.graphics.vector.ImageVector
import de.chaosdorf.meteroid.icons.MeteroidIcons
import de.chaosdorf.meteroid.icons.twotone.WaterFull
enum class HomeSections(
override val title: String,
override val icon: ImageVector,
override val route: String
) : MeteroidNavSection {
PURCHASE("Drinks", MeteroidIcons.TwoTone.WaterFull, Routes.Home.Purchase),
DEPOSIT("Money", Icons.TwoTone.LocalAtm, Routes.Home.Deposit),
HISTORY("History", Icons.TwoTone.History, Routes.Home.History);
}
/*
* 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.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun <T : MeteroidNavSection> MeteroidBottomBar(
currentRoute: T,
navigateTo: (String) -> Unit,
historyEnabled: Boolean,
modifier: Modifier = Modifier
) {
NavigationBar {
for (route in HomeSections.entries) {
NavigationBarItem(
icon = { Icon(route.icon, contentDescription = route.title) },
label = { Text(route.title) },
selected = route == currentRoute,
onClick = { navigateTo(route.route) },
modifier = modifier,
enabled = route != HomeSections.HISTORY || historyEnabled
)
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.navigation
import androidx.compose.ui.graphics.vector.ImageVector
interface MeteroidNavSection {
val title: String
val icon: ImageVector
val route: String
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.navigation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.User
@Composable
fun MeteroidTopBar(
account: Pair<Server, User?>?,
onNavigate: (String) -> Unit = {}
) {
TopAppBar(
title = {
Text(
account?.second?.name
?: account?.first?.name
?: "Meteroid"
)
},
navigationIcon = {
IconButton(onClick = { onNavigate(Routes.Users.Root) }) {
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
}
},
actions = {
account?.second?.let { user ->
val (foreground, background) =
if (user.balance < 0)
Pair(MaterialTheme.colorScheme.onError, MaterialTheme.colorScheme.error)
else
Pair(MaterialTheme.colorScheme.onPrimary, MaterialTheme.colorScheme.primary)
Text(
String.format("%.02f €", user.balance),
color = foreground,
fontSize = 14.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(end = 20.dp)
.clip(RoundedCornerShape(16.dp))
.background(background)
.padding(horizontal = 8.dp)
)
}
},
modifier = Modifier.shadow(4.dp)
)
}
/*
* 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
object Routes {
const val Init = "init"
object Servers {
const val Root = "servers"
const val List = "$Root/list"
const val Add = "$Root/new"
}
object Users {
const val Root = "users"
const val List = "$Root/list"
//const val Add = "$Root/new"
}
object Home {
const val Root = "home"
const val Deposit = "$Root/deposit"
const val Purchase = "$Root/purchase"
const val History = "$Root/history"
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchases
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AttachMoney
import androidx.compose.material.icons.filled.QuestionMark
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.Purchase
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toLocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
@Composable
fun PurchaseListItem(
purchase: Purchase,
drink: Drink?
) {
val timestamp = purchase.createdAt.toLocalDateTime(TimeZone.currentSystemDefault())
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
ListItem(
headlineContent = {
val label =
if (drink != null) drink.name
else if (purchase.difference > 0.0) "Deposit"
else "Unknown"
Text(label)
},
supportingContent = {
Text(formatter.format(timestamp.toJavaLocalDateTime()))
},
leadingContent = {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer)
.aspectRatio(1.0f)
) {
if (drink != null) {
val thumbPainter = rememberAsyncImagePainter(
drink.logoUrl
)
val originalPainter = rememberAsyncImagePainter(
drink.logoUrl.replace("/thumb/", "/original/"),
error = thumbPainter
)
Image(
painter = originalPainter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.align(Alignment.Center)
.fillMaxSize()
)
} else if (purchase.difference > 0) {
Icon(
Icons.Default.AttachMoney,
contentDescription = null,
modifier = Modifier.align(Alignment.Center)
)
} else {
Icon(
Icons.Default.QuestionMark,
contentDescription = null,
modifier = Modifier.align(Alignment.Center)
)
}
}
},
trailingContent = {
val (foreground, background) =
if (purchase.difference < 0)
Pair(MaterialTheme.colorScheme.onError, MaterialTheme.colorScheme.error)
else
Pair(MaterialTheme.colorScheme.onPrimary, MaterialTheme.colorScheme.primary)
Text(
String.format("%.02f €", purchase.difference),
color = foreground,
fontSize = 14.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(background)
.padding(horizontal = 8.dp)
)
}
)
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchases
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import de.chaosdorf.meteroid.sync.SyncHandler
import de.chaosdorf.meteroid.ui.navigation.HomeSections
import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
@Composable
fun PurchaseListScreen(
viewModel: PurchaseViewModel,
onNavigate: (route: String) -> Unit = {}
) {
val account by viewModel.account.collectAsState()
val purchases by viewModel.purchases.collectAsState()
val syncState by viewModel.syncState.collectAsState()
Scaffold(
topBar = { MeteroidTopBar(account, onNavigate) },
bottomBar = {
MeteroidBottomBar(
currentRoute = HomeSections.HISTORY,
historyEnabled = account?.second?.audit == true,
navigateTo = onNavigate
)
}
) { paddingValues: PaddingValues ->
Column {
if (syncState == SyncHandler.State.Loading) {
LinearProgressIndicator()
}
LazyColumn(modifier = Modifier.padding(paddingValues)) {
items(purchases) { (purchase, drink) ->
PurchaseListItem(purchase, drink)
}
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchases
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Purchase
import de.chaosdorf.meteroid.model.PurchaseRepository
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.PurchaseSyncHandler
import de.chaosdorf.meteroid.sync.SyncHandler
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import kotlin.time.Duration.Companion.minutes
@HiltViewModel
class PurchaseViewModel @Inject constructor(
accountProvider: AccountProvider,
repository: PurchaseRepository,
drinkRepository: DrinkRepository,
syncHandler: PurchaseSyncHandler
) : ViewModel() {
val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val purchases: StateFlow<List<Pair<Purchase, Drink?>>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, user) ->
user?.let { user ->
combine(
repository.getAllFlow(server.serverId, user.userId),
drinkRepository.getAllFlow(server.serverId)
) { purchases, drinks ->
purchases.map { purchase ->
Pair(purchase, drinks.firstOrNull { drink -> drink.drinkId == purchase.drinkId })
}
}
}
} ?: flowOf(emptyList())
}.mapLatest { list ->
list.mergeAdjecentDeposits()
.filter { it.second != null || it.first.difference != 0.0 }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val syncState: StateFlow<SyncHandler.State> = syncHandler.state
}
fun List<Pair<Purchase, Drink?>>.mergeAdjecentDeposits(): List<Pair<Purchase, Drink?>> {
val result = mutableListOf<Pair<Purchase, Drink?>>()
for (entry in this) {
val previous = result.lastOrNull()
if (previous != null
&& previous.first.difference > 0
&& entry.first.difference > 0
&& previous.second == null
&& entry.second == null
&& entry.first.createdAt.minus(previous.first.createdAt) < 5.minutes
) {
result.removeLast()
result.add(
Pair(
entry.first.copy(difference = entry.first.difference + previous.first.difference),
null
)
)
} else {
result.add(entry)
}
}
return result
}
/*
* 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.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import kotlinx.coroutines.launch
@Composable
fun AddServerScreen(
viewModel: AddServerViewModel = viewModel(),
onAdd: () -> Unit = {}
) {
val scope = rememberCoroutineScope()
val url by viewModel.url.collectAsState()
val server by viewModel.server.collectAsState()
Column {
TextField(
label = { Text("Server URL") },
value = url,
onValueChange = { viewModel.url.value = it }
)
Button(onClick = {
scope.launch {
viewModel.addServer()
onAdd()
}
}) {
Text("Add Server")
}
server?.let { server ->
Text(server.url)
Text(server.name ?: "null1")
AsyncImage(model = server.logoUrl, contentDescription = null)
}
}
}
/*
* 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.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.mete.v1.MeteApiV1Factory
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.util.findBestIcon
import de.chaosdorf.meteroid.util.resolve
import kotlinx.coroutines.flow.MutableStateFlow
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 kotlin.time.Duration.Companion.milliseconds
@HiltViewModel
class AddServerViewModel @Inject constructor(
private val repository: ServerRepository
) : ViewModel() {
val url = MutableStateFlow("")
private suspend fun buildServer(
id: ServerId,
url: String
): Server? = try {
val api = MeteApiV1Factory.newInstance(url)
val manifest = api.getManifest()
val icon = manifest?.findBestIcon()
Server(
id,
manifest?.name,
url,
icon?.resolve(url)
)
} catch (_: Exception) {
null
}
val server: StateFlow<Server?> = url
.debounce(300.milliseconds)
.mapLatest { buildServer(ServerId(-1), it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
suspend fun addServer() {
val highestServerId = repository.getAll().maxOfOrNull { it.serverId.value } ?: 0
val serverId = ServerId(highestServerId + 1)
val server = buildServer(serverId, url.value)
if (server != null) {
repository.save(server)
}
}
}
/*
* 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.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.chaosdorf.meteroid.model.ServerId
@Preview
@Composable
fun ServerListScreen(
viewModel: ServerListViewModel = viewModel(),
onAdd: () -> Unit = {},
onSelect: (ServerId) -> Unit = {}
) {
val servers by viewModel.servers.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text("Meteroid") },
modifier = Modifier.shadow(4.dp)
)
}
) { paddingValues ->
Column {
LazyColumn(modifier = Modifier.padding(paddingValues)) {
items(servers) { server ->
ListItem(
headlineContent = { Text(server.name ?: server.url) },
supportingContent = { if (server.name != null) Text(server.url) },
modifier = Modifier.clickable { onSelect(server.serverId) }
)
}
item {
ListItem(
headlineContent = { Text("Add Server") },
modifier = Modifier.clickable { onAdd() }
)
}
}
}
}
}
/*
* 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.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class ServerListViewModel @Inject constructor(
serverRepository: ServerRepository
) : ViewModel() {
val servers: StateFlow<List<Server>> = serverRepository.getAllFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}