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

feat: redesigned navigation

parent 7df3b85b
Branches
No related tags found
No related merge requests found
Showing
with 109 additions and 71 deletions
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
![logo](/metadata/en-US/images/featureGraphic.png) ![logo](/metadata/en-US/images/featureGraphic.png)
### What ### What
Small Android application to use with mete, the Matekasse of Chaosdorf. Small Android application to use with mete, the Matekasse of Chaosdorf.
### Where ### Where
* [Chaosdorf Wiki](https://wiki.chaosdorf.de/Meteroid) * [Chaosdorf Wiki](https://wiki.chaosdorf.de/Meteroid)
* [Google Play](https://play.google.com/store/apps/details?id=de.chaosdorf.meteroid2) * [Google Play](https://play.google.com/store/apps/details?id=de.chaosdorf.meteroid2)
* [F-Droid](https://f-droid.org/repository/browse/?fdid=de.chaosdorf.meteroid) * [F-Droid](https://f-droid.org/repository/browse/?fdid=de.chaosdorf.meteroid)
...@@ -20,6 +22,7 @@ Small Android application to use with mete, the Matekasse of Chaosdorf. ...@@ -20,6 +22,7 @@ Small Android application to use with mete, the Matekasse of Chaosdorf.
![your logs](/metadata/en-US/images/phoneScreenshots/3.png) ![your logs](/metadata/en-US/images/phoneScreenshots/3.png)
### License ### License
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2016 Chaosdorf e.V. Copyright (c) 2013-2016 Chaosdorf e.V.
...@@ -46,17 +49,20 @@ THE SOFTWARE. ...@@ -46,17 +49,20 @@ THE SOFTWARE.
The meteroid app stores a few things permanently on your device The meteroid app stores a few things permanently on your device
("permanently" meaning, until you uninstall the app or clear its data): ("permanently" meaning, until you uninstall the app or clear its data):
* the URL of your chosen mete instance * the URL of your chosen mete instance
* your user ID (just a number that identifies you) * your user ID (just a number that identifies you)
* your last five used items * your last five used items
Additionally, some data may be cached temporarily: Additionally, some data may be cached temporarily:
* your avatar * your avatar
* the avatars of other users * the avatars of other users
* the images of drinks * the images of drinks
The rest of the data is kept in your mete instance. Please check its terms The rest of the data is kept in your mete instance. Please check its terms
and make sure you trust its operators: and make sure you trust its operators:
* your chosen name (this can just be a nickname) * your chosen name (this can just be a nickname)
* your e-mail address (optional) * your e-mail address (optional)
* your usage history (optional) * your usage history (optional)
...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable ...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable
@JvmInline @JvmInline
value class BarcodeId(val value: Long) { value class BarcodeId(val value: Long) {
override fun toString() = value.toString() override fun toString() = value.toString()
fun isValid() = value >= 0L
} }
...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable ...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable
@JvmInline @JvmInline
value class DrinkId(val value: Long) { value class DrinkId(val value: Long) {
override fun toString() = value.toString() override fun toString() = value.toString()
fun isValid() = value >= 0L
} }
...@@ -24,9 +24,6 @@ ...@@ -24,9 +24,6 @@
package de.chaosdorf.mete.model package de.chaosdorf.mete.model
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import retrofit2.http.Query
import java.math.BigDecimal import java.math.BigDecimal
interface MeteApi { interface MeteApi {
......
/*
* 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.mete.model
@JvmInline
value class ServerId(val value: Long) {
override fun toString() = value.toString()
fun isValid() = value >= 0L
}
...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable ...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable
@JvmInline @JvmInline
value class TransactionId(val value: Long) { value class TransactionId(val value: Long) {
override fun toString() = value.toString() override fun toString() = value.toString()
fun isValid() = value >= 0L
} }
...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable ...@@ -30,4 +30,5 @@ import kotlinx.serialization.Serializable
@JvmInline @JvmInline
value class UserId(val value: Long) { value class UserId(val value: Long) {
override fun toString() = value.toString() override fun toString() = value.toString()
fun isValid() = value >= 0L
} }
...@@ -24,20 +24,11 @@ ...@@ -24,20 +24,11 @@
package de.chaosdorf.mete.v1 package de.chaosdorf.mete.v1
import de.chaosdorf.mete.model.BarcodeId import de.chaosdorf.mete.model.*
import de.chaosdorf.mete.model.DrinkId
import de.chaosdorf.mete.model.MeteApi
import de.chaosdorf.mete.model.PwaManifest
import de.chaosdorf.mete.model.TransactionSummaryModel
import de.chaosdorf.mete.model.UserId
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
import java.math.BigDecimal import java.math.BigDecimal
import java.time.Year
internal interface MeteApiV1 : MeteApi { internal interface MeteApiV1 : MeteApi {
@GET("manifest.json") @GET("manifest.json")
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/application_name" android:label="@string/application_name"
android:supportsRtl="true" android:supportsRtl="true"
android:hardwareAccelerated="true"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
......
...@@ -27,13 +27,14 @@ package de.chaosdorf.meteroid ...@@ -27,13 +27,14 @@ package de.chaosdorf.meteroid
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get import androidx.lifecycle.get
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import de.chaosdorf.meteroid.ui.AppRouter import de.chaosdorf.meteroid.theme.MeteroidTheme
import de.chaosdorf.meteroid.ui.AppViewModel import de.chaosdorf.meteroid.ui.MeteroidRouter
import de.chaosdorf.meteroid.ui.theme.MeteroidTheme
@AndroidEntryPoint @AndroidEntryPoint
...@@ -41,15 +42,19 @@ class MainActivity : ComponentActivity() { ...@@ -41,15 +42,19 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val viewModelProvider = ViewModelProvider(this) val viewModelProvider = ViewModelProvider(this)
val viewModel = viewModelProvider.get<AppViewModel>() val viewModel = viewModelProvider.get<MeteroidViewModel>()
installSplashScreen().setKeepOnScreenCondition { installSplashScreen().setKeepOnScreenCondition {
viewModel.initialBackStack.value == null viewModel.initialAccount.value == null
} }
setContent { setContent {
val initialAccount by viewModel.initialAccount.collectAsState()
MeteroidTheme { MeteroidTheme {
AppRouter(viewModel) if (initialAccount != null) {
MeteroidRouter(initialAccount!!)
}
} }
} }
} }
......
...@@ -22,32 +22,36 @@ ...@@ -22,32 +22,36 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.servers package de.chaosdorf.meteroid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.ServerRepository import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.storage.AccountPreferences import de.chaosdorf.meteroid.storage.AccountPreferences
import kotlinx.coroutines.flow.SharingStarted import de.chaosdorf.meteroid.sync.SyncManager
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ServerListViewModel @Inject constructor( class MeteroidViewModel @Inject constructor(
accountPreferences: AccountPreferences,
serverRepository: ServerRepository, serverRepository: ServerRepository,
private val preferences: AccountPreferences syncManager: SyncManager
) : ViewModel() { ) : ViewModel() {
val servers: StateFlow<List<Server>> = serverRepository.getAllFlow() val initialAccount: StateFlow<AccountPreferences.State?> = accountPreferences.state
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) .filterNotNull()
.take(1)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
fun selectServer(serverId: ServerId) { init {
viewModelScope.launch { viewModelScope.launch {
preferences.setServer(serverId) serverRepository.getAllFlow().collectLatest { list ->
for (server in list) {
syncManager.sync(server, null, incremental = true)
}
}
} }
} }
} }
...@@ -34,11 +34,7 @@ import dagger.hilt.components.SingletonComponent ...@@ -34,11 +34,7 @@ import dagger.hilt.components.SingletonComponent
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.v1.MeteApiV1Factory import de.chaosdorf.mete.v1.MeteApiV1Factory
import de.chaosdorf.meteroid.MeteroidDatabase import de.chaosdorf.meteroid.MeteroidDatabase
import de.chaosdorf.meteroid.model.DrinkRepository import de.chaosdorf.meteroid.model.*
import de.chaosdorf.meteroid.model.PinnedUserRepository
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.TransactionRepository
import de.chaosdorf.meteroid.model.UserRepository
import javax.inject.Singleton import javax.inject.Singleton
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
package de.chaosdorf.meteroid.storage package de.chaosdorf.meteroid.storage
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.ServerId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface AccountPreferences { interface AccountPreferences {
......
...@@ -29,8 +29,8 @@ import androidx.datastore.core.DataStore ...@@ -29,8 +29,8 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.ServerId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import javax.inject.Inject import javax.inject.Inject
...@@ -38,7 +38,6 @@ import javax.inject.Inject ...@@ -38,7 +38,6 @@ import javax.inject.Inject
class AccountPreferencesImpl @Inject constructor( class AccountPreferencesImpl @Inject constructor(
private val dataStore: DataStore<Preferences> private val dataStore: DataStore<Preferences>
) : AccountPreferences { ) : AccountPreferences {
override val state: Flow<AccountPreferences.State> = override val state: Flow<AccountPreferences.State> =
dataStore.data.mapLatest { dataStore.data.mapLatest {
val serverId = it[SERVER_KEY] ?: -1L val serverId = it[SERVER_KEY] ?: -1L
......
...@@ -24,13 +24,10 @@ ...@@ -24,13 +24,10 @@
package de.chaosdorf.meteroid.sync package de.chaosdorf.meteroid.sync
import android.util.Log
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.*
import de.chaosdorf.meteroid.model.PinnedUser
import de.chaosdorf.meteroid.model.PinnedUserRepository
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.UserRepository
import javax.inject.Inject import javax.inject.Inject
class AccountProvider @Inject constructor( class AccountProvider @Inject constructor(
...@@ -49,8 +46,10 @@ class AccountProvider @Inject constructor( ...@@ -49,8 +46,10 @@ class AccountProvider @Inject constructor(
suspend fun togglePin(serverId: ServerId, userId: UserId) { suspend fun togglePin(serverId: ServerId, userId: UserId) {
if (pinnedUserRepository.isPinned(serverId, userId)) { if (pinnedUserRepository.isPinned(serverId, userId)) {
Log.e("DEBUG", "Unpinning $serverId, $userId")
pinnedUserRepository.delete(serverId, userId) pinnedUserRepository.delete(serverId, userId)
} else { } else {
Log.e("DEBUG", "Pinning $serverId, $userId")
pinnedUserRepository.save(PinnedUser(serverId, userId)) pinnedUserRepository.save(PinnedUser(serverId, userId))
} }
} }
......
...@@ -27,11 +27,11 @@ package de.chaosdorf.meteroid.sync ...@@ -27,11 +27,11 @@ package de.chaosdorf.meteroid.sync
import androidx.room.withTransaction import androidx.room.withTransaction
import de.chaosdorf.mete.model.DrinkId import de.chaosdorf.mete.model.DrinkId
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.meteroid.MeteroidDatabase import de.chaosdorf.meteroid.MeteroidDatabase
import de.chaosdorf.meteroid.model.Drink import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.DrinkRepository import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.sync.base.BaseSyncHandler import de.chaosdorf.meteroid.sync.base.BaseSyncHandler
import javax.inject.Inject import javax.inject.Inject
......
...@@ -26,12 +26,10 @@ package de.chaosdorf.meteroid.sync ...@@ -26,12 +26,10 @@ package de.chaosdorf.meteroid.sync
import android.util.Log import android.util.Log
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.*
import de.chaosdorf.meteroid.model.Drink import de.chaosdorf.meteroid.sync.base.SyncHandler
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.util.newServer import de.chaosdorf.meteroid.util.newServer
import kotlinx.coroutines.flow.combine
import java.math.BigDecimal import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
...@@ -42,6 +40,16 @@ class SyncManager @Inject constructor( ...@@ -42,6 +40,16 @@ class SyncManager @Inject constructor(
private val drinkSyncHandler: DrinkSyncHandler, private val drinkSyncHandler: DrinkSyncHandler,
private val transactionSyncHandler: TransactionSyncHandler private val transactionSyncHandler: TransactionSyncHandler
) { ) {
val syncState = combine(
userSyncHandler.state,
drinkSyncHandler.state,
transactionSyncHandler.state
) { states ->
states.find { it is SyncHandler.State.Error }
?: states.find { it == SyncHandler.State.Loading }
?: SyncHandler.State.Idle
}
suspend fun checkOffline(server: Server): Boolean { suspend fun checkOffline(server: Server): Boolean {
val updated = factory.newServer(server.serverId, server.url) val updated = factory.newServer(server.serverId, server.url)
return if (updated == null) { return if (updated == null) {
...@@ -76,14 +84,16 @@ class SyncManager @Inject constructor( ...@@ -76,14 +84,16 @@ class SyncManager @Inject constructor(
} }
} }
suspend fun purchase(account: AccountInfo, drink: Drink) { suspend fun purchase(account: AccountInfo, drink: Drink, count: Int) {
val api = factory.newInstance(account.server.url) val api = factory.newInstance(account.server.url)
try { try {
Log.i( Log.i(
"SyncManager", "SyncManager",
"Syncing purchase of ${drink.drinkId}/${drink.drinkId} for ${account.user.name}/${account.user.userId}" "Syncing purchase of ${drink.drinkId}/${drink.drinkId} for ${account.user.name}/${account.user.userId}"
) )
for (i in 0 until count) {
api.purchase(account.user.userId, drink.drinkId) api.purchase(account.user.userId, drink.drinkId)
}
sync(account.server, account.user, incremental = true) sync(account.server, account.user, incremental = true)
} catch (e: Exception) { } catch (e: Exception) {
Log.e( Log.e(
......
...@@ -26,20 +26,15 @@ package de.chaosdorf.meteroid.sync ...@@ -26,20 +26,15 @@ package de.chaosdorf.meteroid.sync
import androidx.room.withTransaction import androidx.room.withTransaction
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.TransactionId import de.chaosdorf.mete.model.TransactionId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.MeteroidDatabase import de.chaosdorf.meteroid.MeteroidDatabase
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.Transaction import de.chaosdorf.meteroid.model.Transaction
import de.chaosdorf.meteroid.model.TransactionRepository import de.chaosdorf.meteroid.model.TransactionRepository
import de.chaosdorf.meteroid.sync.base.BaseIncrementalSyncHandler import de.chaosdorf.meteroid.sync.base.BaseIncrementalSyncHandler
import kotlinx.datetime.Clock import kotlinx.datetime.*
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
import javax.inject.Inject import javax.inject.Inject
class TransactionSyncHandler @Inject constructor( class TransactionSyncHandler @Inject constructor(
......
...@@ -26,10 +26,10 @@ package de.chaosdorf.meteroid.sync ...@@ -26,10 +26,10 @@ package de.chaosdorf.meteroid.sync
import androidx.room.withTransaction import androidx.room.withTransaction
import de.chaosdorf.mete.model.MeteApiFactory import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.ServerId
import de.chaosdorf.mete.model.UserId import de.chaosdorf.mete.model.UserId
import de.chaosdorf.meteroid.MeteroidDatabase import de.chaosdorf.meteroid.MeteroidDatabase
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.model.UserRepository import de.chaosdorf.meteroid.model.UserRepository
import de.chaosdorf.meteroid.sync.base.BaseSyncHandler import de.chaosdorf.meteroid.sync.base.BaseSyncHandler
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment