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 583 additions and 352 deletions
......@@ -24,22 +24,20 @@
package de.chaosdorf.meteroid.ui.transactions
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.AccountInfo
import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.TransactionRepository
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncHandler
import de.chaosdorf.meteroid.sync.SyncManager
import de.chaosdorf.meteroid.sync.TransactionSyncHandler
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 kotlinx.coroutines.launch
......@@ -49,48 +47,40 @@ import kotlin.time.Duration.Companion.minutes
@HiltViewModel
class TransactionViewModel @Inject constructor(
private val accountProvider: AccountProvider,
savedStateHandle: SavedStateHandle,
accountProvider: AccountProvider,
private val syncManager: SyncManager,
repository: TransactionRepository,
drinkRepository: DrinkRepository,
private val syncManager: SyncManager
drinkRepository: DrinkRepository
) : ViewModel() {
val account: StateFlow<AccountInfo?> = accountProvider.account
private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
private val userId = UserId(checkNotNull(savedStateHandle["user"]))
val account = accountProvider.accountFlow(serverId, userId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val transactions: StateFlow<List<TransactionInfo>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, maybeUser) ->
maybeUser?.let { user ->
combine(
repository.getAllFlow(server.serverId, user.userId),
drinkRepository.getAllFlow(server.serverId)
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 }
)
}
TransactionInfo(transaction,
drinks.firstOrNull { drink -> drink.drinkId == transaction.drinkId })
}
}
} ?: flowOf(emptyList())
}.mapLatest { list ->
list.mergeAdjecentDeposits()
.filter { it.drink != null || it.transaction.difference != BigDecimal.ZERO }
list.mergeAdjecentDeposits().filter {
it.drink != null || it.transaction.difference.abs() >= 0.01.toBigDecimal()
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
fun togglePin() {
account.value?.let { account ->
account.user?.let { user ->
fun sync() {
account.value?.let { (server, user) ->
viewModelScope.launch {
accountProvider.togglePin(account.server.serverId, user.userId)
}
syncManager.sync(server, user, incremental = true)
}
}
}
suspend fun checkOffline(server: Server?): Boolean =
if (server == null) true
suspend fun checkOffline(server: Server?): Boolean = if (server == null) true
else syncManager.checkOffline(server)
}
......@@ -98,12 +88,9 @@ 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
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(
......
/*
* 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.users
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.UserId
import de.chaosdorf.meteroid.model.ServerId
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)
}
)
}
......@@ -24,136 +24,75 @@
package de.chaosdorf.meteroid.ui.users
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import de.chaosdorf.mete.model.UserId
import okhttp3.HttpUrl.Companion.toHttpUrl
import androidx.navigation.NavController
import de.chaosdorf.meteroid.ui.navigation.Routes
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun UserListScreen(
navController: NavController,
viewModel: UserListViewModel,
onAdd: () -> Unit = {},
onSelect: (UserId) -> Unit = {},
onBack: () -> Unit = {},
contentPadding: PaddingValues = PaddingValues(),
) {
val server by viewModel.account.collectAsState()
val users by viewModel.users.collectAsState()
val pinnedUsers by viewModel.pinnedUsers.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = {
Row {
AsyncImage(
server?.server?.logoUrl,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(48.dp)
)
Spacer(Modifier.width(16.dp))
Column(modifier = Modifier.align(Alignment.CenterVertically)) {
if (server?.server != null) {
if (server?.server?.name != null) {
Text(
server!!.server.name!!,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyMedium
)
Text(
server!!.server.url.toHttpUrl().host,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.67f),
fontWeight = FontWeight.Medium,
style = MaterialTheme.typography.bodyMedium
)
} else {
Text(
server!!.server.url,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyMedium
)
}
} else {
Text(
"Meteroid",
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
},
navigationIcon = {
IconButton(onClick = { onBack() }) {
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = null)
}
},
modifier = Modifier
.shadow(4.dp)
.clickable { onBack() }
)
}
) { paddingValues ->
LazyVerticalGrid(
GridCells.Adaptive(80.dp),
modifier = Modifier.padding(horizontal = 8.dp),
contentPadding = paddingValues
) {
LazyColumn(contentPadding = contentPadding) {
if (pinnedUsers.isNotEmpty()) {
item("pinned", span = { GridItemSpan(maxLineSpan) }) {
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) { user ->
UserTile(user, onSelect)
items(
pinnedUsers,
key = { "pinned-${it.userId}" },
) { user ->
UserListItem(user) { serverId, userId ->
navController.navigate(Routes.Home.purchase(serverId, userId))
viewModel.selectUser(serverId, userId)
}
}
}
for (character in 'A'..'Z') {
val group = users.filter { it.name.startsWith(character, ignoreCase = true) }
if (group.isNotEmpty()) {
item(character.toString(), span = { GridItemSpan(maxLineSpan) }) {
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) { user ->
UserTile(user, onSelect)
items(
group,
key = { "${it.serverId}-${it.userId}" },
) { user ->
UserListItem(user) { serverId, userId ->
navController.navigate(Routes.Home.purchase(serverId, userId))
viewModel.selectUser(serverId, userId)
}
}
}
......
......@@ -24,50 +24,48 @@
package de.chaosdorf.meteroid.ui.users
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.meteroid.model.AccountInfo
import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.PinnedUserRepository
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.model.UserRepository
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.UserSyncHandler
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.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class UserListViewModel @Inject constructor(
accountProvider: AccountProvider,
savedStateHandle: SavedStateHandle,
userRepository: UserRepository,
pinnedUserRepository: PinnedUserRepository,
private val preferences: AccountPreferences,
) : ViewModel() {
val account: StateFlow<AccountInfo?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
val users: StateFlow<List<User>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, _) ->
userRepository.getAllFlow(server.serverId)
} ?: flowOf(emptyList())
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val users: StateFlow<List<User>> = userRepository.getAllFlow(serverId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val pinnedUsers = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, _) ->
combine(
userRepository.getAllFlow(server.serverId),
pinnedUserRepository.getAllFlow(server.serverId)
val pinnedUsers = combine(
userRepository.getAllFlow(serverId),
pinnedUserRepository.getAllFlow(serverId)
) { users, pinned ->
users.filter { pinned.contains(it.userId) }
}
} ?: flowOf(emptyList())
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)
}
}
}
......@@ -30,70 +30,31 @@ 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.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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 androidx.navigation.NavOptions
import coil.compose.rememberAsyncImagePainter
import de.chaosdorf.meteroid.R
import de.chaosdorf.meteroid.ui.navigation.HomeSections
import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import java.time.Month
import java.time.format.TextStyle
@Composable
fun WrappedScreen(
viewModel: WrappedViewModel,
onNavigate: (String, NavOptions) -> Unit
contentPadding: PaddingValues = PaddingValues(),
) {
val account by viewModel.account.collectAsState()
val slides by viewModel.slides.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(account) {
val offline = viewModel.checkOffline(account?.server)
snackbarHostState.currentSnackbarData?.dismiss()
if (offline) {
snackbarHostState.showSnackbar(
message = "Unable to connect to server",
duration = SnackbarDuration.Indefinite
)
}
}
Scaffold(
topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) },
bottomBar = {
MeteroidBottomBar(
currentRoute = HomeSections.WRAPPED,
historyEnabled = account?.user?.audit == true,
wrappedEnabled = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
.month.let { it == Month.NOVEMBER || it == Month.DECEMBER },
navigateTo = onNavigate
)
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { paddingValues: PaddingValues ->
LazyColumn(contentPadding = paddingValues) {
items(slides) { slide ->
LazyColumn(contentPadding = contentPadding) {
items(
slides,
key = { "${it::class.qualifiedName}" },
) { slide ->
when (slide) {
is WrappedSlide.MostBoughtDrink ->
ListItem(
......@@ -185,4 +146,3 @@ fun WrappedScreen(
}
}
}
}
......@@ -95,7 +95,7 @@ sealed class WrappedSlide {
dosage,
Animal.values()
.sortedBy(Animal::lethalDosage)
.firstOrNull { it.lethalDosage < dosage }
.lastOrNull { it.lethalDosage < dosage }
)
}
}
......
......@@ -24,24 +24,24 @@
package de.chaosdorf.meteroid.ui.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.meteroid.model.AccountInfo
import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.Transaction
import de.chaosdorf.meteroid.model.TransactionRepository
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncManager
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.stateIn
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
......@@ -52,21 +52,31 @@ import javax.inject.Inject
@HiltViewModel
class WrappedViewModel @Inject constructor(
private val accountProvider: AccountProvider,
savedStateHandle: SavedStateHandle,
accountProvider: AccountProvider,
private val syncManager: SyncManager,
repository: TransactionRepository,
drinkRepository: DrinkRepository,
private val syncManager: SyncManager
) : ViewModel() {
val account: StateFlow<AccountInfo?> = accountProvider.account
private val serverId = ServerId(checkNotNull(savedStateHandle["server"]))
private val userId = UserId(checkNotNull(savedStateHandle["user"]))
val account = accountProvider.accountFlow(serverId, userId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val slides: StateFlow<List<WrappedSlide>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, maybeUser) ->
maybeUser?.let { user ->
combine(
repository.getAllFlow(server.serverId, user.userId),
drinkRepository.getAllFlow(server.serverId)
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(
......@@ -74,43 +84,11 @@ class WrappedViewModel @Inject constructor(
WrappedSlide.Caffeine,
WrappedSlide.MostActive
)
val timeZone = TimeZone.currentSystemDefault()
val now = Clock.System.now().toLocalDateTime(timeZone)
val yearBegin = LocalDateTime(
year = now.year,
month = Month.JANUARY,
dayOfMonth = 1,
hour = 0,
minute = 0,
second = 0
).toInstant(timeZone)
val yearEnd = LocalDateTime(
year = now.year + 1,
month = Month.JANUARY,
dayOfMonth = 1,
hour = 0,
minute = 0,
second = 0
).toInstant(timeZone)
val thisYear = transactions.filter {
it.timestamp in yearBegin..yearEnd
}
factories.mapNotNull { it.create(thisYear, drinkMap) }
}
}
} ?: flowOf(emptyList())
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())
fun togglePin() {
account.value?.let { account ->
account.user?.let { user ->
viewModelScope.launch {
accountProvider.togglePin(account.server.serverId, user.userId)
}
}
}
}
suspend fun checkOffline(server: Server?): Boolean =
if (server == null) true
else syncManager.checkOffline(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.util
import androidx.navigation.NavController
fun NavController.popUpToRoot() {
while (popBackStack()) {
// repeat
}
}
/*
* 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.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.RenderVectorGroup
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.Dp
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
@Composable
fun rememberAvatarPainter(url: String?, iconWidth: Dp, iconHeight: Dp, iconTint: Color): AsyncImagePainter {
val personPainter = rememberVectorPainter(
defaultHeight = iconHeight,
defaultWidth = iconWidth,
viewportWidth = Icons.Filled.Person.viewportWidth,
viewportHeight = Icons.Filled.Person.viewportHeight,
name = Icons.Filled.Person.name,
tintColor = iconTint,
tintBlendMode = Icons.Filled.Person.tintBlendMode,
autoMirror = Icons.Filled.Person.autoMirror,
content = { _, _ -> RenderVectorGroup(group = Icons.Filled.Person.root) }
)
return rememberAsyncImagePainter(url, fallback = personPainter)
}
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:width="72dp"
android:height="72dp"
android:viewportWidth="72"
android:viewportHeight="72">
<path
android:fillColor="#003984"
android:pathData="m11.5 0c-6.35 0-11.5 5.15-11.5 11.5v49c0 6.35 5.15 11.5 11.5 11.5h49c6.35 0 11.5-5.15 11.5-11.5v-49c0-6.35-5.15-11.5-11.5-11.5z" />
<path
android:fillAlpha="0.5"
android:fillColor="#000000"
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">
<aapt:attr name="android:fillColor">
<gradient
android:endX="0"
android:endY="60"
android:startX="0"
android:startY="20"
android:type="linear">
<item
android:color="#ff9800"
android:offset="0.6" />
<item
android:color="#f9c579"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#bad9ff"
android:pathData="m36 15.4c-5.15 0-9.85 0.237-13.3 0.625-1.71 0.194-3.09 0.422-4.09 0.69-0.5 0.135-0.9 0.273-1.24 0.469-0.17 0.098-0.329 0.21-0.474 0.39-0.144 0.18-0.268 0.475-0.232 0.77l5 42.2c0.072 0.61 0.53 1.08 1.12 1.4 0.59 0.314 1.38 0.545 2.46 0.745 2.17 0.398 5.55 0.63 10.7 0.63s8.55-0.233 10.7-0.63c1.08-0.199 1.87-0.432 2.46-0.745 0.59-0.314 1.05-0.785 1.12-1.39l5-42.2c0.0353-0.298-0.088-0.59-0.232-0.77s-0.305-0.292-0.474-0.39c-0.339-0.196-0.74-0.334-1.24-0.469-1-0.27-2.38-0.499-4.09-0.69-3.42-0.387-8.1-0.625-13.3-0.625zm0 1.5c5.1 0 9.8 0.238 13.1 0.615 1.66 0.188 3 0.415 3.87 0.65 0.398 0.108 0.665 0.218 0.815 0.297l-4.98 42c0.0034-0.0282 0.021 0.0545-0.337 0.245-0.358 0.19-1.02 0.412-2.02 0.595-2 0.366-5.35 0.605-10.4 0.605s-8.45-0.24-10.4-0.605c-1-0.183-1.66-0.404-2.02-0.595-0.358-0.19-0.34-0.274-0.337-0.245l-4.98-42c0.15-0.0785 0.416-0.189 0.815-0.297 0.865-0.234 2.2-0.46 3.87-0.65 3.33-0.377 8-0.615 13.1-0.615z"
tools:ignore="VectorPath" />
<path
android:fillColor="#ffe2b7"
android:pathData="m27 26.4a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm18 4.49a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm-19.4 6.2a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm3.92 3.85a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 0 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm12.2 0.725a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-4.46 4.02a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-6.35 1.4a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm11.6 1.92a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-5.15 1.54a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.48a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm10.4 3.06a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.132a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm3.72 1.64a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm2.63 2.34a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845z"
tools:ignore="VectorPath" />
<path
android:fillColor="#d91616"
android:pathData="m36 26.3 3.06 6.45 7.05 0.92-5.15 4.9 1.3 7-6.25-3.4-6.25 3.4 1.3-7-5.15-4.9 7.05-0.92z" />
</vector>
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillAlpha="0.5"
android:fillColor="#000000"
android:pathData="m39.7 78.9h28.6l28 29.1h-56.5z" />
<path android:pathData="m71.5 41.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">
<gradient
android:endX="0"
android:endY="80"
android:startX="0"
android:startY="40"
android:type="linear">
<item
android:color="#ff9800"
android:offset="0.6" />
<item
android:color="#f9c579"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#bad9ff"
android:pathData="m54 33.4c-5.15 0-9.85 0.237-13.3 0.625-1.71 0.194-3.09 0.422-4.09 0.69-0.5 0.135-0.9 0.273-1.24 0.469-0.17 0.098-0.329 0.21-0.474 0.39-0.144 0.18-0.268 0.475-0.232 0.77l5 42.2c0.072 0.61 0.53 1.08 1.12 1.4 0.59 0.314 1.38 0.545 2.46 0.745 2.17 0.398 5.55 0.63 10.7 0.63s8.55-0.233 10.7-0.63c1.08-0.199 1.87-0.432 2.46-0.745 0.59-0.314 1.05-0.785 1.12-1.39l5-42.2c0.0353-0.298-0.088-0.59-0.232-0.77s-0.305-0.292-0.474-0.39c-0.339-0.196-0.74-0.334-1.24-0.469-1-0.27-2.38-0.499-4.09-0.69-3.42-0.387-8.1-0.625-13.3-0.625zm0 1.5c5.1 0 9.8 0.238 13.1 0.615 1.66 0.188 3 0.415 3.87 0.65 0.398 0.108 0.665 0.218 0.815 0.297l-4.98 42c0.0034-0.0282 0.021 0.0545-0.337 0.245-0.358 0.19-1.02 0.412-2.02 0.595-2 0.366-5.35 0.605-10.4 0.605s-8.45-0.24-10.4-0.605c-1-0.183-1.66-0.404-2.02-0.595-0.358-0.19-0.34-0.274-0.337-0.245l-4.98-42c0.15-0.0785 0.416-0.189 0.815-0.297 0.865-0.234 2.2-0.46 3.87-0.65 3.33-0.377 8-0.615 13.1-0.615z"
tools:ignore="VectorPath" />
<path
android:fillColor="#ffe2b7"
android:pathData="m45 44.4a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm18 4.49a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm-19.4 6.2a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm3.92 3.85a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 0 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm12.2 0.725a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-4.46 4.02a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-6.35 1.4a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm11.6 1.92a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-5.15 1.54a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.48a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm10.4 3.06a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.132a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm3.72 1.64a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm2.63 2.34a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845z"
tools:ignore="VectorPath" />
<path
android:fillColor="#d91616"
android:pathData="m54 44.3 3.06 6.45 7.05 0.92-5.15 4.9 1.3 7-6.25-3.4-6.25 3.4 1.3-7-5.15-4.9 7.05-0.92z" />
</vector>
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="m71.5 41.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">
<gradient
android:endX="0"
android:endY="80"
android:startX="0"
android:startY="40"
android:type="linear">
<item
android:color="#ff9800"
android:offset="0.6" />
<item
android:color="#f9c579"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#bad9ff"
android:pathData="m54 33.4c-5.15 0-9.85 0.237-13.3 0.625-1.71 0.194-3.09 0.422-4.09 0.69-0.5 0.135-0.9 0.273-1.24 0.469-0.17 0.098-0.329 0.21-0.474 0.39-0.144 0.18-0.268 0.475-0.232 0.77l5 42.2c0.072 0.61 0.53 1.08 1.12 1.4 0.59 0.314 1.38 0.545 2.46 0.745 2.17 0.398 5.55 0.63 10.7 0.63s8.55-0.233 10.7-0.63c1.08-0.199 1.87-0.432 2.46-0.745 0.59-0.314 1.05-0.785 1.12-1.39l5-42.2c0.0353-0.298-0.088-0.59-0.232-0.77s-0.305-0.292-0.474-0.39c-0.339-0.196-0.74-0.334-1.24-0.469-1-0.27-2.38-0.499-4.09-0.69-3.42-0.387-8.1-0.625-13.3-0.625zm0 1.5c5.1 0 9.8 0.238 13.1 0.615 1.66 0.188 3 0.415 3.87 0.65 0.398 0.108 0.665 0.218 0.815 0.297l-4.98 42c0.0034-0.0282 0.021 0.0545-0.337 0.245-0.358 0.19-1.02 0.412-2.02 0.595-2 0.366-5.35 0.605-10.4 0.605s-8.45-0.24-10.4-0.605c-1-0.183-1.66-0.404-2.02-0.595-0.358-0.19-0.34-0.274-0.337-0.245l-4.98-42c0.15-0.0785 0.416-0.189 0.815-0.297 0.865-0.234 2.2-0.46 3.87-0.65 3.33-0.377 8-0.615 13.1-0.615z"
tools:ignore="VectorPath" />
<path
android:fillColor="#ffe2b7"
android:pathData="m45 44.4a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm18 4.49a2.96 2.96 0 0 0-2.74 2.96 2.98 2.98 0 0 0 5.95 0 2.96 2.96 0 0 0-3.18-2.96zm-19.4 6.2a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm3.92 3.85a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 0 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm12.2 0.725a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-4.46 4.02a1.7 1.7 0 0 0-1.56 1.69 1.7 1.7 0 1 0 3.39 0 1.7 1.7 0 0 0-1.82-1.69zm-6.35 1.4a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm11.6 1.92a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-5.15 1.54a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.48a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm10.4 3.06a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm-7.75 0.132a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 1 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm3.72 1.64a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845zm2.63 2.34a0.845 0.845 0 0 0-0.785 0.845 0.848 0.848 0 0 0 1.7 0 0.845 0.845 0 0 0-0.91-0.845z"
tools:ignore="VectorPath" />
<path
android:fillColor="#d91616"
android:pathData="m54 44.3 3.06 6.45 7.05 0.92-5.15 4.9 1.3 7-6.25-3.4-6.25 3.4 1.3-7-5.15-4.9 7.05-0.92z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
app/src/main/res/mipmap-hdpi/ic_launcher.webp

2.24 KiB

app/src/main/res/mipmap-hdpi/ic_launcher_round.webp

3.93 KiB

app/src/main/res/mipmap-mdpi/ic_launcher.webp

1.57 KiB

app/src/main/res/mipmap-mdpi/ic_launcher_round.webp

2.5 KiB

app/src/main/res/mipmap-xhdpi/ic_launcher.webp

3.1 KiB

app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp

5.61 KiB