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

wip: progress

parent fc6492ad
No related branches found
No related tags found
No related merge requests found
Showing
with 121 additions and 36 deletions
...@@ -29,16 +29,31 @@ import de.chaosdorf.mete.model.MeteApiFactory ...@@ -29,16 +29,31 @@ import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.AccountInfo
import de.chaosdorf.meteroid.model.Drink import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.util.newServer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.math.BigDecimal import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
class SyncManager @Inject constructor( class SyncManager @Inject constructor(
private val factory: MeteApiFactory, private val factory: MeteApiFactory,
private val serverRepository: ServerRepository,
private val userSyncHandler: UserSyncHandler, private val userSyncHandler: UserSyncHandler,
private val drinkSyncHandler: DrinkSyncHandler, private val drinkSyncHandler: DrinkSyncHandler,
private val transactionSyncHandler: TransactionSyncHandler private val transactionSyncHandler: TransactionSyncHandler
) { ) {
suspend fun checkOffline(server: Server): Boolean {
val updated = factory.newServer(server.serverId, server.url)
return if (updated == null) {
true
} else {
serverRepository.save(updated)
false
}
}
suspend fun sync(server: Server, user: User?) { suspend fun sync(server: Server, user: User?) {
try { try {
userSyncHandler.sync(server) userSyncHandler.sync(server)
......
...@@ -43,7 +43,7 @@ import androidx.navigation.compose.NavHost ...@@ -43,7 +43,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import de.chaosdorf.meteroid.ui.Transactions.TransactionListScreen import de.chaosdorf.meteroid.ui.transactions.TransactionListScreen
import de.chaosdorf.meteroid.ui.drinks.DrinkListScreen import de.chaosdorf.meteroid.ui.drinks.DrinkListScreen
import de.chaosdorf.meteroid.ui.drinks.DrinkListViewModel import de.chaosdorf.meteroid.ui.drinks.DrinkListViewModel
import de.chaosdorf.meteroid.ui.money.MoneyListScreen import de.chaosdorf.meteroid.ui.money.MoneyListScreen
......
...@@ -34,7 +34,11 @@ import androidx.compose.foundation.lazy.grid.GridCells ...@@ -34,7 +34,11 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
...@@ -63,6 +67,18 @@ fun DrinkListScreen( ...@@ -63,6 +67,18 @@ fun DrinkListScreen(
val account by viewModel.account.collectAsState() val account by viewModel.account.collectAsState()
val drinks by viewModel.drinks.collectAsState() val drinks by viewModel.drinks.collectAsState()
val filters by viewModel.filters.collectAsState() val filters by viewModel.filters.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( Scaffold(
topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) }, topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) },
...@@ -72,6 +88,9 @@ fun DrinkListScreen( ...@@ -72,6 +88,9 @@ fun DrinkListScreen(
historyEnabled = account?.user?.audit == true, historyEnabled = account?.user?.audit == true,
navigateTo = onNavigate navigateTo = onNavigate
) )
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
} }
) { paddingValues: PaddingValues -> ) { paddingValues: PaddingValues ->
Column(Modifier.padding(paddingValues)) { Column(Modifier.padding(paddingValues)) {
......
...@@ -31,6 +31,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel ...@@ -31,6 +31,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.AccountInfo
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.sync.AccountProvider import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncManager import de.chaosdorf.meteroid.sync.SyncManager
import de.chaosdorf.meteroid.util.update import de.chaosdorf.meteroid.util.update
...@@ -97,6 +98,10 @@ class DrinkListViewModel @Inject constructor( ...@@ -97,6 +98,10 @@ class DrinkListViewModel @Inject constructor(
} }
} }
suspend fun checkOffline(server: Server?): Boolean =
if (server == null) true
else syncManager.checkOffline(server)
enum class Filter { enum class Filter {
CaffeineFree, CaffeineFree,
Active; Active;
......
...@@ -32,7 +32,11 @@ import androidx.compose.foundation.lazy.grid.GridCells ...@@ -32,7 +32,11 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
...@@ -58,6 +62,18 @@ fun MoneyListScreen( ...@@ -58,6 +62,18 @@ fun MoneyListScreen(
} }
} }
val account by viewModel.account.collectAsState() val account by viewModel.account.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( Scaffold(
topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) }, topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) },
...@@ -67,6 +83,9 @@ fun MoneyListScreen( ...@@ -67,6 +83,9 @@ fun MoneyListScreen(
historyEnabled = account?.user?.audit == true, historyEnabled = account?.user?.audit == true,
navigateTo = onNavigate navigateTo = onNavigate
) )
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
} }
) { paddingValues: PaddingValues -> ) { paddingValues: PaddingValues ->
Column { Column {
......
...@@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope ...@@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.R import de.chaosdorf.meteroid.R
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.AccountInfo
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.sync.AccountProvider import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncManager import de.chaosdorf.meteroid.sync.SyncManager
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
...@@ -79,4 +80,8 @@ class MoneyListViewModel @Inject constructor( ...@@ -79,4 +80,8 @@ class MoneyListViewModel @Inject constructor(
} }
} }
} }
suspend fun checkOffline(server: Server?): Boolean =
if (server == null) true
else syncManager.checkOffline(server)
} }
...@@ -31,8 +31,7 @@ import de.chaosdorf.mete.model.MeteApiFactory ...@@ -31,8 +31,7 @@ import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerId import de.chaosdorf.meteroid.model.ServerId
import de.chaosdorf.meteroid.model.ServerRepository import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.util.findBestIcon import de.chaosdorf.meteroid.util.newServer
import de.chaosdorf.meteroid.util.resolve
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
...@@ -49,32 +48,15 @@ class AddServerViewModel @Inject constructor( ...@@ -49,32 +48,15 @@ class AddServerViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
val url = MutableStateFlow("") val url = MutableStateFlow("")
private suspend fun buildServer(
id: ServerId,
url: String
): Server? = try {
val api = factory.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 val server: StateFlow<Server?> = url
.debounce(300.milliseconds) .debounce(300.milliseconds)
.mapLatest { buildServer(ServerId(-1), it) } .mapLatest { factory.newServer(ServerId(-1), it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
suspend fun addServer() { suspend fun addServer() {
val highestServerId = repository.getAll().maxOfOrNull { it.serverId.value } ?: 0 val highestServerId = repository.getAll().maxOfOrNull { it.serverId.value } ?: 0
val serverId = ServerId(highestServerId + 1) val serverId = ServerId(highestServerId + 1)
val server = buildServer(serverId, url.value) val server = factory.newServer(serverId, url.value)
if (server != null) { if (server != null) {
repository.save(server) repository.save(server)
} }
......
...@@ -22,23 +22,24 @@ ...@@ -22,23 +22,24 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.chaosdorf.meteroid.ui.Transactions package de.chaosdorf.meteroid.ui.transactions
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.runtime.remember
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import de.chaosdorf.meteroid.ui.navigation.HomeSections import de.chaosdorf.meteroid.ui.navigation.HomeSections
import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar import de.chaosdorf.meteroid.ui.navigation.MeteroidBottomBar
import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar import de.chaosdorf.meteroid.ui.navigation.MeteroidTopBar
import de.chaosdorf.meteroid.ui.transactions.TransactionListItem
import de.chaosdorf.meteroid.ui.transactions.TransactionViewModel
@Composable @Composable
fun TransactionListScreen( fun TransactionListScreen(
...@@ -47,6 +48,18 @@ fun TransactionListScreen( ...@@ -47,6 +48,18 @@ fun TransactionListScreen(
) { ) {
val account by viewModel.account.collectAsState() val account by viewModel.account.collectAsState()
val transactions by viewModel.transactions.collectAsState() val transactions by viewModel.transactions.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( Scaffold(
topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) }, topBar = { MeteroidTopBar(account, onNavigate, viewModel::togglePin) },
...@@ -56,6 +69,9 @@ fun TransactionListScreen( ...@@ -56,6 +69,9 @@ fun TransactionListScreen(
historyEnabled = account?.user?.audit == true, historyEnabled = account?.user?.audit == true,
navigateTo = onNavigate navigateTo = onNavigate
) )
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
} }
) { paddingValues: PaddingValues -> ) { paddingValues: PaddingValues ->
LazyColumn(contentPadding = paddingValues) { LazyColumn(contentPadding = paddingValues) {
......
...@@ -29,9 +29,11 @@ import androidx.lifecycle.viewModelScope ...@@ -29,9 +29,11 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.AccountInfo import de.chaosdorf.meteroid.model.AccountInfo
import de.chaosdorf.meteroid.model.DrinkRepository import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.TransactionRepository import de.chaosdorf.meteroid.model.TransactionRepository
import de.chaosdorf.meteroid.sync.AccountProvider import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncHandler import de.chaosdorf.meteroid.sync.SyncHandler
import de.chaosdorf.meteroid.sync.SyncManager
import de.chaosdorf.meteroid.sync.TransactionSyncHandler import de.chaosdorf.meteroid.sync.TransactionSyncHandler
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
...@@ -49,7 +51,8 @@ import kotlin.time.Duration.Companion.minutes ...@@ -49,7 +51,8 @@ import kotlin.time.Duration.Companion.minutes
class TransactionViewModel @Inject constructor( class TransactionViewModel @Inject constructor(
private val accountProvider: AccountProvider, private val accountProvider: AccountProvider,
repository: TransactionRepository, repository: TransactionRepository,
drinkRepository: DrinkRepository drinkRepository: DrinkRepository,
private val syncManager: SyncManager
) : ViewModel() { ) : ViewModel() {
val account: StateFlow<AccountInfo?> = accountProvider.account val account: StateFlow<AccountInfo?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
...@@ -85,6 +88,10 @@ class TransactionViewModel @Inject constructor( ...@@ -85,6 +88,10 @@ class TransactionViewModel @Inject constructor(
} }
} }
} }
suspend fun checkOffline(server: Server?): Boolean =
if (server == null) true
else syncManager.checkOffline(server)
} }
fun List<TransactionInfo>.mergeAdjecentDeposits(): List<TransactionInfo> { fun List<TransactionInfo>.mergeAdjecentDeposits(): List<TransactionInfo> {
......
...@@ -24,13 +24,30 @@ ...@@ -24,13 +24,30 @@
package de.chaosdorf.meteroid.util package de.chaosdorf.meteroid.util
import de.chaosdorf.mete.model.PwaIcon import de.chaosdorf.mete.model.MeteApiFactory
import de.chaosdorf.mete.model.PwaManifest import de.chaosdorf.meteroid.model.Server
import okhttp3.HttpUrl.Companion.toHttpUrl import de.chaosdorf.meteroid.model.ServerId
import java.net.URI
fun PwaManifest.findBestIcon(): PwaIcon? = icons.maxByOrNull { suspend fun MeteApiFactory.newServer(serverId: ServerId, baseUrl: String): Server? = try {
it.sizes?.split("x")?.firstOrNull()?.toIntOrNull() ?: 0 val api = newInstance(baseUrl)
val manifest = api.getManifest()
val icon = manifest?.icons?.maxByOrNull {
it.sizes?.split("x")
?.mapNotNull(String::toIntOrNull)
?.reduce(Int::times)
?: 0
} }
fun PwaIcon.resolve(baseUrl: String): String? = val iconUrl = icon?.src?.let { URI(baseUrl).resolve(it).toString() }
this.src?.let { baseUrl.toHttpUrl().resolve(it) }?.toString()
Server(
serverId,
manifest?.name,
baseUrl,
iconUrl
)
} catch (_: Exception) {
null
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment