feat(connectivity): add offline resilience with server reachability checks

This commit is contained in:
2026-06-07 11:25:59 +00:00
parent 3f9fd6e36e
commit 4a6977eefb
11 changed files with 317 additions and 54 deletions

View File

@@ -13,11 +13,11 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
@@ -46,6 +46,7 @@ fun AppScreen(
val nextUp by viewModel.nextUp.collectAsStateWithLifecycle()
val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
val isOnline by viewModel.isOnline.collectAsStateWithLifecycle()
val isCheckingConnection by viewModel.isCheckingConnection.collectAsStateWithLifecycle()
val isCheckingForUpdates by updateViewModel.isCheckingForUpdates.collectAsStateWithLifecycle()
val availableUpdate by updateViewModel.availableUpdate.collectAsStateWithLifecycle()
val navigationManager = LocalNavigationManager.current
@@ -133,6 +134,8 @@ fun AppScreen(
DownloadsScreen(
selectedTab = selectedTab,
isOnline = isOnline,
isCheckingConnection = isCheckingConnection,
onCheckConnection = viewModel::checkConnection,
onTabSelected = onTabSelected,
modifier = Modifier.fillMaxSize()
)

View File

@@ -10,12 +10,15 @@ import androidx.compose.ui.Modifier
import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.download.components.DownloadsContent
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarTextButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DownloadsScreen(
selectedTab: Int,
isOnline: Boolean,
isCheckingConnection: Boolean,
onCheckConnection: () -> Unit,
onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier
) {
@@ -23,7 +26,19 @@ fun DownloadsScreen(
modifier = modifier.fillMaxSize(),
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
topBar = { DefaultTopBar() },
topBar = {
DefaultTopBar(
rightActions = {
if (!isOnline) {
DefaultTopBarTextButton(
text = if (isCheckingConnection) "Checking…" else "Check connection",
onClick = onCheckConnection,
enabled = !isCheckingConnection
)
}
}
)
},
bottomBar = {
AppBottomBar(
selectedTab = selectedTab,

View File

@@ -5,14 +5,17 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
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.outlined.Download
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text

View File

@@ -5,18 +5,23 @@ import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
@@ -114,6 +119,37 @@ fun DefaultTopBarIconButton(
}
}
@Composable
fun DefaultTopBarTextButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
val scheme = MaterialTheme.colorScheme
TextButton(
onClick = onClick,
enabled = enabled,
shape = CircleShape,
colors = ButtonDefaults.textButtonColors(
containerColor = scheme.surface,
contentColor = scheme.onSurface,
disabledContainerColor = scheme.surface,
disabledContentColor = scheme.onSurface
),
contentPadding = PaddingValues(horizontal = 18.dp),
modifier = modifier
.height(50.dp)
.clip(CircleShape),
) {
Text(
text = text,
style = MaterialTheme.typography.labelLarge,
)
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun DefaultTopBarSearchButton(