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
  • main
1 result

Target

Select target project
  • justJanne/meteroid
1 result
Select Git revision
  • main
1 result
Show changes
Showing
with 1444 additions and 0 deletions
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/lib/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# The project is open source anyway, obfuscation is useless.
-dontobfuscate
# remove unnecessary warnings
# Android HTTP Libs
-dontnote android.net.http.**
-dontnote org.apache.http.**
# Kotlin stuff
-dontnote kotlin.**
# Gson
-dontnote com.google.gson.**
# Dagger
-dontwarn com.google.errorprone.annotations.*
# Retrofit
-dontwarn retrofit2.**
# Annotation used by Retrofit on Java 8 VMs
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
-dontwarn javax.annotation.concurrent.GuardedBy
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
# Okio
-dontwarn okio.**
-dontwarn org.conscrypt.**
# OkHttp3
-dontwarn okhttp3.**
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MeteroidApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"
android:supportsRtl="true"
android:theme="@style/Theme.Meteroid"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Meteroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
/*
* 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
import de.chaosdorf.mete.UserId
import de.chaosdorf.meteroid.model.ServerId
import kotlinx.coroutines.flow.Flow
interface AccountPreferences {
data class State(
val server: ServerId?,
val user: UserId?
)
val state: Flow<State>
suspend fun setServer(server: ServerId?)
suspend fun setUser(userId: UserId?)
}
/*
* 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
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import de.chaosdorf.mete.UserId
import de.chaosdorf.meteroid.model.ServerId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest
import javax.inject.Inject
class AccountPreferencesImpl @Inject constructor(
private val dataStore: DataStore<Preferences>
) : AccountPreferences {
override val state: Flow<AccountPreferences.State> =
dataStore.data.mapLatest {
val serverId = it[SERVER_KEY] ?: -1L
val userId = it[USER_KEY] ?: -1L
AccountPreferences.State(
if (serverId >= 0) ServerId(serverId) else null,
if (userId >= 0) UserId(userId) else null,
)
}
override suspend fun setServer(server: ServerId?) {
dataStore.edit {
it[SERVER_KEY] = server?.value ?: -1L
}
}
override suspend fun setUser(userId: UserId?) {
dataStore.edit {
it[SERVER_KEY] = userId?.value ?: -1L
}
}
private companion object {
val SERVER_KEY = longPreferencesKey("serverId")
val USER_KEY = longPreferencesKey("userId")
}
}
/*
* 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
import androidx.room.withTransaction
import de.chaosdorf.mete.DrinkId
import de.chaosdorf.mete.v1.MeteApiV1Factory
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 javax.inject.Inject
class DrinkSyncHandler @Inject constructor(
private val db: MeteroidDatabase,
private val repository: DrinkRepository
) : SyncHandler<Server, Drink, DrinkSyncHandler.Key>() {
data class Key(
val server: ServerId, val drink: DrinkId
)
override suspend fun withTransaction(block: suspend () -> Unit) =
db.withTransaction(block)
override suspend fun store(entry: Drink) =
repository.save(entry)
override suspend fun delete(key: Key) =
repository.delete(key.server, key.drink)
override fun entryToKey(entry: Drink) = Key(entry.serverId, entry.drinkId)
override suspend fun loadStored(context: Server): List<Drink> =
repository.getAll(context.serverId)
override suspend fun loadCurrent(context: Server): List<Drink> {
val api = MeteApiV1Factory.newInstance(context.url)
val loadedEntries = api.listDrinks()
return loadedEntries.map { Drink.fromModelV1(context.serverId, it) }
}
}
/*
* 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
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.twotone.Money
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.mete.v1.MeteApiV1Factory
import de.chaosdorf.meteroid.icons.MeteroidIcons
import de.chaosdorf.meteroid.icons.twotone.WaterFull
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.ServerRepository
import de.chaosdorf.meteroid.ui.theme.MeteroidTheme
import de.chaosdorf.meteroid.util.findBestIcon
import de.chaosdorf.meteroid.util.resolve
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
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
import kotlin.time.Duration.Companion.milliseconds
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var accountPreferences: AccountPreferences
@Inject
lateinit var serverRepository: ServerRepository
@Inject
lateinit var drinkSyncHandler: DrinkSyncHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val server = accountPreferences.state
.mapLatest { it.server }
.flatMapLatest {
it?.let { serverRepository.getFlow(it) }
?: flowOf<Server?>(null)
}
lifecycleScope.launch {
server.collectLatest {
it?.let { server ->
drinkSyncHandler.sync(server)
}
}
}
setContent {
MeteroidTheme {
AppRouter()
}
}
}
}
@HiltViewModel
class AppViewModel @Inject constructor(
private val accountPreferences: AccountPreferences,
private val serverRepository: ServerRepository,
private val drinkSyncHandler: DrinkSyncHandler
) : ViewModel() {
val server: StateFlow<Server?> = accountPreferences.state
.mapLatest { it.server }
.flatMapLatest { serverId ->
serverRepository.getAllFlow()
.mapLatest { list ->
list.firstOrNull { server -> server.serverId == serverId }
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
init {
viewModelScope.launch {
server.collectLatest {
it?.let { server ->
drinkSyncHandler.sync(server)
}
}
}
}
suspend fun selectServer(server: ServerId) {
accountPreferences.setServer(server)
}
}
@Composable
fun AppRouter(viewModel: AppViewModel = viewModel()) {
val scope = rememberCoroutineScope()
val server by viewModel.server.collectAsState()
val navController = rememberNavController()
LaunchedEffect(server) {
if (server == null) {
navController.navigate("accounts/list")
}
}
Scaffold(topBar = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
if (currentDestination?.hierarchy?.any { it.route == "main" } == true) {
TopAppBar(title = { Text("Meteroid") }, navigationIcon = {
IconButton(onClick = { navController.navigate("accounts") }) {
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = null)
}
})
} else if (currentDestination?.hierarchy?.any { it.route == "accounts/list" } == true) {
TopAppBar(title = { Text("Meteroid") }, navigationIcon = {
IconButton(onClick = { navController.navigate("main") }) {
Icon(Icons.Default.Close, contentDescription = null)
}
})
}
}, bottomBar = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
if (currentDestination?.hierarchy?.any { it.route == "main" } == true) {
BottomAppBar(actions = {
IconButton(onClick = { navController.navigate("main/purchase") }) {
Icon(MeteroidIcons.TwoTone.WaterFull, contentDescription = null)
}
IconButton(onClick = { navController.navigate("main/deposit") }) {
Icon(Icons.TwoTone.Money, contentDescription = null)
}
})
}
}) { padding ->
NavHost(navController, startDestination = "main", Modifier.padding(padding)) {
navigation(route = "accounts", startDestination = "accounts/list") {
composable("accounts/list") { backStackEntry ->
ServerListScreen(
hiltViewModel(),
onAddServer = { navController.navigate("accounts/addServer") },
onSelectServer = {
scope.launch {
viewModel.selectServer(it)
navController.navigate("main")
}
}
)
}
composable("accounts/addServer") { backStackEntry ->
AddServerScreen(
hiltViewModel(),
onAddServer = { navController.navigate("accounts/list") }
)
}
}
navigation(route = "main", startDestination = "main/purchase") {
/*
composable("users") { backStackEntry ->
val viewModel = hiltViewModel<UserListViewModel>()
UserListScreen(viewModel)
}
*/
composable("main/purchase") { backStackEntry ->
DrinkListScreen(hiltViewModel())
}
composable("main/deposit") { backStackEntry ->
}
}
}
}
}
@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)
}
}
}
@Composable
fun AddServerScreen(viewModel: AddServerViewModel, onAddServer: () -> 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()
onAddServer()
}
}) {
Text("Add Server")
}
server?.let { server ->
Text(server.url)
Text(server.name ?: "null1")
AsyncImage(model = server.logoUrl, contentDescription = null)
}
}
}
@Preview
@Composable
fun ServerListScreen(
viewModel: ServerListViewModel = viewModel(),
onAddServer: () -> Unit = {},
onSelectServer: (ServerId) -> Unit = {}
) {
val servers by viewModel.servers.collectAsState()
LazyColumn {
items(servers) { server ->
ListItem(
headlineContent = { Text(server.name ?: server.url) },
modifier = Modifier.clickable {
onSelectServer(server.serverId)
}
)
}
item {
ListItem(
headlineContent = { Text("Add Server") },
modifier = Modifier.clickable {
onAddServer()
}
)
}
}
}
@Preview
@Composable
fun DrinkListScreen(
viewModel: DrinkListViewModel = viewModel()
) {
val drinks by viewModel.drinks.collectAsState()
LazyColumn {
items(drinks) { drink ->
ListItem(headlineContent = { Text(drink.name) },
supportingContent = { Text("${drink.volume}l · ${drink.price}€") })
}
}
}
@HiltViewModel
class DrinkListViewModel @Inject constructor(
drinkRepository: DrinkRepository,
accountPreferences: AccountPreferences
) : ViewModel() {
private val serverId: Flow<ServerId?> = accountPreferences.state.mapLatest { it.server }
val drinks: StateFlow<List<Drink>> = serverId.flatMapLatest {
it?.let { serverId ->
drinkRepository.getAllFlow(serverId)
} ?: flowOf(emptyList())
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}
@HiltViewModel
class ServerListViewModel @Inject constructor(
serverRepository: ServerRepository
) : ViewModel() {
val servers: StateFlow<List<Server>> = serverRepository.getAllFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}
/*
* 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
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MeteroidApplication : Application()
/*
* 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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
abstract class SyncHandler<Context, Entry, Key> {
sealed class State {
data object Idle : State()
data object Loading : State()
data class Error(val message: String) : State()
}
abstract suspend fun withTransaction(block: suspend () -> Unit)
abstract suspend fun loadCurrent(context: Context): List<Entry>
abstract suspend fun loadStored(context: Context): List<Entry>
abstract fun entryToKey(entry: Entry): Key
abstract suspend fun delete(key: Key)
abstract suspend fun store(entry: Entry)
private val _state = MutableStateFlow<State>(State.Idle)
val state: StateFlow<State> = _state
suspend fun sync(context: Context) {
if (_state.compareAndSet(State.Idle, State.Loading)) {
try {
val loadedEntries = loadCurrent(context)
withTransaction {
val storedEntries = loadStored(context)
val storedKeys = storedEntries.map(::entryToKey).toSet()
val loadedKeys = loadedEntries.map(::entryToKey).toSet()
val removedKeys = storedKeys - loadedKeys
for (removedKey in removedKeys) {
delete(removedKey)
}
for (loadedEntry in loadedEntries) {
store(loadedEntry)
}
}
_state.value = State.Idle
} catch (e: Exception) {
_state.value = State.Error("Error while syncing data: $e")
}
}
}
}
/*
* 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.di
import android.content.Context
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import de.chaosdorf.meteroid.MeteroidDatabase
import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.UserRepository
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Singleton
@Provides
fun provideDatabase(
@ApplicationContext context: Context
): MeteroidDatabase = Room
.databaseBuilder(context, MeteroidDatabase::class.java, "mete")
.build()
@Provides
fun provideDrinkRepository(
database: MeteroidDatabase
): DrinkRepository = database.drinks()
@Provides
fun provideUserRepository(
database: MeteroidDatabase
): UserRepository = database.users()
@Provides
fun provideServerRepository(
database: MeteroidDatabase
): ServerRepository = database.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.di
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import de.chaosdorf.meteroid.AccountPreferences
import de.chaosdorf.meteroid.AccountPreferencesImpl
import javax.inject.Singleton
val Context.accountDataStore: DataStore<Preferences> by preferencesDataStore(name = "account")
@Module
@InstallIn(SingletonComponent::class)
object PreferenceModule {
@Singleton
@Provides
fun provideAccountPreferences(
@ApplicationContext context: Context
): DataStore<Preferences> = context.accountDataStore
}
@Module
@InstallIn(SingletonComponent::class)
abstract class AccountPreferenceModule {
@Binds
abstract fun bindsAccountPreferences(
impl: AccountPreferencesImpl
): 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.icons
object MeteroidIcons {
object Outlined
object Filled
object TwoTone
}
/*
* 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.icons.filled
import androidx.compose.material.icons.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.icons.MeteroidIcons
public val MeteroidIcons.Filled.WaterFull: ImageVector
get() {
if (_waterFull != null) {
return _waterFull!!
}
_waterFull = ImageVector.Builder(
name = "Filled.WaterFull",
defaultWidth = 24f.dp,
defaultHeight = 24f.dp,
viewportWidth = 960f,
viewportHeight = 960f
).apply {
materialPath {
moveTo(211f, 357f)
quadTo(262f, 325f, 322.5f, 308f)
quadTo(383f, 291f, 444f, 290f)
quadTo(474f, 290f, 503.5f, 294f)
quadTo(533f, 298f, 560f, 306f)
quadTo(611.13f, 320f, 638.23f, 325f)
quadTo(665.32f, 330f, 696f, 330f)
lineTo(752f, 330f)
lineTo(773f, 140f)
lineTo(187f, 140f)
lineTo(211f, 357f)
close()
moveTo(262f, 880f)
quadTo(238.75f, 880f, 221.5f, 865.07f)
quadTo(204.25f, 850.14f, 202f, 827f)
lineTo(120f, 80f)
lineTo(840f, 80f)
lineTo(758f, 827f)
quadTo(755.75f, 850.14f, 738.5f, 865.07f)
quadTo(721.25f, 880f, 698f, 880f)
lineTo(262f, 880f)
close()
moveTo(444f, 820f)
quadTo(470f, 820f, 494.5f, 820f)
quadTo(519f, 820f, 544f, 820f)
quadTo(598f, 820f, 629f, 820f)
quadTo(660f, 820f, 697f, 820f)
lineTo(697f, 820f)
lineTo(697f, 820f)
quadTo(697f, 820f, 697f, 820f)
quadTo(697f, 820f, 697f, 820f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(262f, 820f)
quadTo(277.67f, 820f, 330.83f, 820f)
quadTo(384f, 820f, 444f, 820f)
close()
}
}.build()
return _waterFull!!
}
private var _waterFull: ImageVector? = 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.icons.outlined
import androidx.compose.material.icons.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.icons.MeteroidIcons
public val MeteroidIcons.Outlined.WaterFull: ImageVector
get() {
if (_waterFull != null) {
return _waterFull!!
}
_waterFull = ImageVector.Builder(
name = "Outlined.WaterFull",
defaultWidth = 24f.dp,
defaultHeight = 24f.dp,
viewportWidth = 960f,
viewportHeight = 960f
).apply {
materialPath {
moveTo(444f, 350f)
quadTo(384f, 350f, 325f, 369.5f)
quadTo(266f, 389f, 218f, 424f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(698f, 820f)
quadTo(698f, 820f, 698f, 820f)
quadTo(698f, 820f, 698f, 820f)
lineTo(745f, 390f)
lineTo(697f, 390f)
quadTo(659.75f, 390f, 628.88f, 384.5f)
quadTo(598f, 379f, 544f, 364f)
quadTo(519f, 357f, 494.5f, 353.5f)
quadTo(470f, 350f, 444f, 350f)
close()
moveTo(211f, 357f)
quadTo(262f, 325f, 322.5f, 308f)
quadTo(383f, 291f, 444f, 290f)
quadTo(474f, 290f, 503.5f, 294f)
quadTo(533f, 298f, 560f, 306f)
quadTo(611.13f, 320f, 638.23f, 325f)
quadTo(665.32f, 330f, 696f, 330f)
lineTo(752f, 330f)
lineTo(773f, 140f)
lineTo(187f, 140f)
lineTo(211f, 357f)
close()
moveTo(262f, 880f)
quadTo(238.75f, 880f, 221.5f, 865.07f)
quadTo(204.25f, 850.14f, 202f, 827f)
lineTo(120f, 80f)
lineTo(840f, 80f)
lineTo(758f, 827f)
quadTo(755.75f, 850.14f, 738.5f, 865.07f)
quadTo(721.25f, 880f, 698f, 880f)
lineTo(262f, 880f)
close()
moveTo(444f, 820f)
quadTo(470f, 820f, 494.5f, 820f)
quadTo(519f, 820f, 544f, 820f)
quadTo(598f, 820f, 629f, 820f)
quadTo(660f, 820f, 697f, 820f)
lineTo(697f, 820f)
lineTo(697f, 820f)
quadTo(697f, 820f, 697f, 820f)
quadTo(697f, 820f, 697f, 820f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(262f, 820f)
quadTo(277.67f, 820f, 330.83f, 820f)
quadTo(384f, 820f, 444f, 820f)
close()
}
}.build()
return _waterFull!!
}
private var _waterFull: ImageVector? = 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.icons.twotone
import androidx.compose.material.icons.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.icons.MeteroidIcons
public val MeteroidIcons.TwoTone.WaterFull: ImageVector
get() {
if (_waterFull != null) {
return _waterFull!!
}
_waterFull = ImageVector.Builder(
name = "TwoTone.WaterFull",
defaultWidth = 24f.dp,
defaultHeight = 24f.dp,
viewportWidth = 960f,
viewportHeight = 960f
).apply {
materialPath {
moveTo(444f, 350f)
quadTo(384f, 350f, 325f, 369.5f)
quadTo(266f, 389f, 218f, 424f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(698f, 820f)
quadTo(698f, 820f, 698f, 820f)
quadTo(698f, 820f, 698f, 820f)
lineTo(745f, 390f)
lineTo(697f, 390f)
quadTo(659.75f, 390f, 628.88f, 384.5f)
quadTo(598f, 379f, 544f, 364f)
quadTo(519f, 357f, 494.5f, 353.5f)
quadTo(470f, 350f, 444f, 350f)
close()
moveTo(211f, 357f)
quadTo(262f, 325f, 322.5f, 308f)
quadTo(383f, 291f, 444f, 290f)
quadTo(474f, 290f, 503.5f, 294f)
quadTo(533f, 298f, 560f, 306f)
quadTo(611.13f, 320f, 638.23f, 325f)
quadTo(665.32f, 330f, 696f, 330f)
lineTo(752f, 330f)
lineTo(773f, 140f)
lineTo(187f, 140f)
lineTo(211f, 357f)
close()
moveTo(262f, 880f)
quadTo(238.75f, 880f, 221.5f, 865.07f)
quadTo(204.25f, 850.14f, 202f, 827f)
lineTo(120f, 80f)
lineTo(840f, 80f)
lineTo(758f, 827f)
quadTo(755.75f, 850.14f, 738.5f, 865.07f)
quadTo(721.25f, 880f, 698f, 880f)
lineTo(262f, 880f)
close()
moveTo(444f, 820f)
quadTo(470f, 820f, 494.5f, 820f)
quadTo(519f, 820f, 544f, 820f)
quadTo(598f, 820f, 629f, 820f)
quadTo(660f, 820f, 697f, 820f)
lineTo(697f, 820f)
lineTo(697f, 820f)
quadTo(697f, 820f, 697f, 820f)
quadTo(697f, 820f, 697f, 820f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(262f, 820f)
quadTo(277.67f, 820f, 330.83f, 820f)
quadTo(384f, 820f, 444f, 820f)
close()
}
materialPath(fillAlpha = 0.3f, strokeAlpha = 0.3f) {
moveTo(444f, 350f)
quadTo(384f, 350f, 325f, 369.5f)
quadTo(266f, 389f, 218f, 424f)
lineTo(262f, 820f)
quadTo(262f, 820f, 262f, 820f)
quadTo(262f, 820f, 262f, 820f)
lineTo(698f, 820f)
quadTo(698f, 820f, 698f, 820f)
quadTo(698f, 820f, 698f, 820f)
lineTo(745f, 390f)
lineTo(697f, 390f)
quadTo(659.75f, 390f, 628.88f, 384.5f)
quadTo(598f, 379f, 544f, 364f)
quadTo(519f, 357f, 494.5f, 353.5f)
quadTo(470f, 350f, 444f, 350f)
close()
}
}.build()
return _waterFull!!
}
private var _waterFull: ImageVector? = null
package de.chaosdorf.meteroid.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
/*
* 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.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MeteroidTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
package de.chaosdorf.meteroid.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)
\ No newline at end of file
/*
* 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 kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun Call.await() = suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val body = response.body
if (body == null) {
continuation.resumeWithException(KotlinNullPointerException("Response from ${call.request().method}.${call.request().url} was null but response body type was declared as non-null"))
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(IOException("Response from ${call.request().method}.${call.request().url} returned a non-successful response code: ${response.code}"))
}
}
})
}
/*
* 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 de.chaosdorf.mete.PwaIcon
import de.chaosdorf.mete.PwaManifest
import okhttp3.HttpUrl.Companion.toHttpUrl
fun PwaManifest.findBestIcon(): PwaIcon? = icons.maxByOrNull {
it.sizes?.split("x")?.firstOrNull()?.toIntOrNull() ?: 0
}
fun PwaIcon.resolve(baseUrl: String): String? =
this.src?.let { baseUrl.toHttpUrl().resolve(it) }?.toString()
app/src/main/res/mipmap-xxhdpi/ic_launcher.png

9.69 KiB