Skip to content

Commit 21ad2f5

Browse files
Titouan Thibaudtitooan
authored andcommitted
Add support for Pull to Refresh on the Home screen.
1 parent 2cf1339 commit 21ad2f5

File tree

4 files changed

+109
-77
lines changed

4 files changed

+109
-77
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ dependencies {
107107
implementation(libs.androidx.compose.ui.graphics)
108108
implementation(libs.androidx.compose.ui.tooling.preview)
109109
implementation(libs.androidx.compose.material3)
110+
implementation(libs.androidx.compose.material)
110111
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
111112

112113
// Navigation Compose

app/src/main/java/org/mozilla/tryfox/ui/screens/HomeScreen.kt

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ package org.mozilla.tryfox.ui.screens
22

33
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Box
5-
import androidx.compose.foundation.layout.Column
65
import androidx.compose.foundation.layout.Spacer
76
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.fillMaxWidth
97
import androidx.compose.foundation.layout.height
108
import androidx.compose.foundation.layout.padding
119
import androidx.compose.foundation.lazy.LazyColumn
1210
import androidx.compose.foundation.lazy.items
11+
import androidx.compose.material.ExperimentalMaterialApi
1312
import androidx.compose.material.icons.Icons
1413
import androidx.compose.material.icons.filled.AccountCircle
1514
import androidx.compose.material.icons.filled.Search
15+
import androidx.compose.material.pullrefresh.PullRefreshIndicator
16+
import androidx.compose.material.pullrefresh.pullRefresh
17+
import androidx.compose.material.pullrefresh.rememberPullRefreshState
1618
import androidx.compose.material3.CircularProgressIndicator
1719
import androidx.compose.material3.ExperimentalMaterial3Api
1820
import androidx.compose.material3.Icon
@@ -47,7 +49,7 @@ import org.mozilla.tryfox.ui.models.AppUiModel
4749
import org.mozilla.tryfox.util.parseDateToMillis
4850
import java.io.File
4951

50-
@OptIn(ExperimentalMaterial3Api::class)
52+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
5153
@Composable
5254
fun HomeScreen(
5355
modifier: Modifier = Modifier,
@@ -56,6 +58,8 @@ fun HomeScreen(
5658
homeViewModel: HomeViewModel = viewModel(),
5759
) {
5860
val screenState by homeViewModel.homeScreenState.collectAsState()
61+
val isRefreshing by homeViewModel.isRefreshing.collectAsState()
62+
val pullRefreshState = rememberPullRefreshState(isRefreshing, { homeViewModel.refreshData() })
5963

6064
LaunchedEffect(Unit) {
6165
homeViewModel.initialLoad()
@@ -111,33 +115,36 @@ fun HomeScreen(
111115
)
112116
},
113117
) { innerPadding ->
114-
when (val currentScreenState = screenState) {
115-
is HomeScreenState.InitialLoading -> {
116-
Box(
117-
modifier = Modifier
118-
.fillMaxSize()
119-
.padding(innerPadding),
120-
contentAlignment = Alignment.Center,
121-
) {
122-
CircularProgressIndicator()
123-
Text(
124-
stringResource(id = R.string.home_loading_initial_data),
125-
modifier = Modifier.padding(top = 70.dp), // Adjust as needed to place below indicator
126-
)
118+
Box(
119+
modifier = Modifier
120+
.fillMaxSize()
121+
.padding(innerPadding)
122+
.pullRefresh(pullRefreshState),
123+
) {
124+
when (val currentScreenState = screenState) {
125+
is HomeScreenState.InitialLoading -> {
126+
Box(
127+
modifier = Modifier
128+
.fillMaxSize(),
129+
contentAlignment = Alignment.Center,
130+
) {
131+
CircularProgressIndicator()
132+
Text(
133+
stringResource(id = R.string.home_loading_initial_data),
134+
modifier = Modifier.padding(top = 70.dp), // Adjust as needed to place below indicator
135+
)
136+
}
127137
}
128-
}
129138

130-
is HomeScreenState.Loaded -> {
131-
Column(
132-
modifier = Modifier
133-
.fillMaxSize()
134-
.padding(innerPadding)
135-
.padding(horizontal = 16.dp),
136-
horizontalAlignment = Alignment.CenterHorizontally,
137-
verticalArrangement = Arrangement.Top,
138-
) {
139-
Spacer(modifier = Modifier.height(16.dp))
140-
LazyColumn(modifier = Modifier.fillMaxWidth()) {
139+
is HomeScreenState.Loaded -> {
140+
LazyColumn(
141+
modifier = Modifier
142+
.fillMaxSize()
143+
.padding(horizontal = 16.dp),
144+
horizontalAlignment = Alignment.CenterHorizontally,
145+
verticalArrangement = Arrangement.Top,
146+
) {
147+
item { Spacer(modifier = Modifier.height(16.dp)) }
141148
items(currentScreenState.apps.values.toList()) { app ->
142149
AppComponent(
143150
app = app,
@@ -157,6 +164,12 @@ fun HomeScreen(
157164
}
158165
}
159166
}
167+
168+
PullRefreshIndicator(
169+
refreshing = isRefreshing,
170+
state = pullRefreshState,
171+
modifier = Modifier.align(Alignment.TopCenter),
172+
)
160173
}
161174
}
162175
}

app/src/main/java/org/mozilla/tryfox/ui/screens/HomeViewModel.kt

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class HomeViewModel(
5151
private val _homeScreenState = MutableStateFlow<HomeScreenState>(HomeScreenState.InitialLoading)
5252
val homeScreenState: StateFlow<HomeScreenState> = _homeScreenState.asStateFlow()
5353

54+
private val _isRefreshing = MutableStateFlow(false)
55+
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
56+
5457
private val deviceSupportedAbis: List<String> by lazy {
5558
deviceSupportedAbisForTesting ?: Build.SUPPORTED_ABIS?.toList() ?: emptyList()
5659
}
@@ -108,66 +111,79 @@ class HomeViewModel(
108111
fun initialLoad() {
109112
viewModelScope.launch {
110113
_homeScreenState.value = HomeScreenState.InitialLoading
114+
_isRefreshing.value = true
111115
cacheManager.checkCacheStatus() // Initial check
116+
fetchData()
117+
_isRefreshing.value = false
118+
}
119+
}
112120

113-
val appInfoMap = mapOf(
114-
FENIX to mozillaPackageManager.fenix,
115-
FOCUS to mozillaPackageManager.focus,
116-
REFERENCE_BROWSER to mozillaPackageManager.referenceBrowser,
117-
)
118-
119-
_homeScreenState.update {
120-
val currentCacheState = cacheManager.cacheState.value
121-
val initialApps = appInfoMap.mapValues { (appName, appState) ->
122-
AppUiModel(
123-
name = appName,
124-
packageName = appState.packageName,
125-
installedVersion = appState.version,
126-
installedDate = appState.formattedInstallDate,
127-
apks = ApksResult.Loading,
128-
)
129-
}
130-
HomeScreenState.Loaded(
131-
apps = initialApps,
132-
cacheManagementState = currentCacheState,
133-
isDownloadingAnyFile = false,
134-
)
135-
}
121+
fun refreshData() {
122+
viewModelScope.launch {
123+
_isRefreshing.value = true
124+
fetchData()
125+
_isRefreshing.value = false
126+
}
127+
}
136128

137-
val results = mapOf(
138-
FENIX to mozillaArchiveRepository.getFenixNightlyBuilds(),
139-
FOCUS to mozillaArchiveRepository.getFocusNightlyBuilds(),
140-
REFERENCE_BROWSER to mozillaArchiveRepository.getReferenceBrowserNightlyBuilds(),
141-
)
129+
private suspend fun fetchData() {
130+
val appInfoMap = mapOf(
131+
FENIX to mozillaPackageManager.fenix,
132+
FOCUS to mozillaPackageManager.focus,
133+
REFERENCE_BROWSER to mozillaPackageManager.referenceBrowser,
134+
)
142135

143-
val newApps = results.mapValues { (appName, result) ->
144-
val appState = appInfoMap[appName]
145-
val apksResult = when (result) {
146-
is NetworkResult.Success -> {
147-
val latestApks = getLatestApks(result.data)
148-
ApksResult.Success(convertParsedApksToUiModels(latestApks))
149-
}
150-
is NetworkResult.Error -> ApksResult.Error("Error fetching $appName nightly builds: ${result.message}")
151-
}
136+
_homeScreenState.update {
137+
val currentCacheState = cacheManager.cacheState.value
138+
val initialApps = appInfoMap.mapValues { (appName, appState) ->
152139
AppUiModel(
153140
name = appName,
154-
packageName = appState?.packageName ?: "",
155-
installedVersion = appState?.version,
156-
installedDate = appState?.formattedInstallDate,
157-
apks = apksResult,
141+
packageName = appState.packageName,
142+
installedVersion = appState.version,
143+
installedDate = appState.formattedInstallDate,
144+
apks = ApksResult.Loading,
158145
)
159146
}
147+
HomeScreenState.Loaded(
148+
apps = initialApps,
149+
cacheManagementState = currentCacheState,
150+
isDownloadingAnyFile = false,
151+
)
152+
}
160153

161-
val isDownloading = newApps.values.any { app ->
162-
(app.apks as? ApksResult.Success)?.apks?.any { it.downloadState is DownloadState.InProgress } == true
163-
}
154+
val results = mapOf(
155+
FENIX to mozillaArchiveRepository.getFenixNightlyBuilds(),
156+
FOCUS to mozillaArchiveRepository.getFocusNightlyBuilds(),
157+
REFERENCE_BROWSER to mozillaArchiveRepository.getReferenceBrowserNightlyBuilds(),
158+
)
164159

165-
_homeScreenState.update {
166-
if (it is HomeScreenState.Loaded) {
167-
it.copy(apps = newApps, isDownloadingAnyFile = isDownloading)
168-
} else {
169-
it
160+
val newApps = results.mapValues { (appName, result) ->
161+
val appState = appInfoMap[appName]
162+
val apksResult = when (result) {
163+
is NetworkResult.Success -> {
164+
val latestApks = getLatestApks(result.data)
165+
ApksResult.Success(convertParsedApksToUiModels(latestApks))
170166
}
167+
is NetworkResult.Error -> ApksResult.Error("Error fetching $appName nightly builds: ${result.message}")
168+
}
169+
AppUiModel(
170+
name = appName,
171+
packageName = appState?.packageName ?: "",
172+
installedVersion = appState?.version,
173+
installedDate = appState?.formattedInstallDate,
174+
apks = apksResult,
175+
)
176+
}
177+
178+
val isDownloading = newApps.values.any { app ->
179+
(app.apks as? ApksResult.Success)?.apks?.any { it.downloadState is DownloadState.InProgress } == true
180+
}
181+
182+
_homeScreenState.update {
183+
if (it is HomeScreenState.Loaded) {
184+
it.copy(apps = newApps, isDownloadingAnyFile = isDownloading)
185+
} else {
186+
it
171187
}
172188
}
173189
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mockitoKotlin = "6.0.0"
2525
logcat = "0.4"
2626
espressoIntents = "3.7.0" # Added javax.inject version
2727
koin = "4.1.1"
28+
composeMaterial = "1.6.8"
2829

2930
[libraries]
3031
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -45,6 +46,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
4546
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
4647
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
4748
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
49+
androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "composeMaterial" }
4850
androidx-compose-material3-adaptive-navigation-suite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" }
4951
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
5052
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }

0 commit comments

Comments
 (0)