Skip to content

Commit d246298

Browse files
authored
Merge pull request #358 from OpenHub-Store/clear-cache-enhancment
2 parents 69cb26b + d829571 commit d246298

10 files changed

Lines changed: 176 additions & 34 deletions

File tree

core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AndroidDownloader.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ class AndroidDownloader(
210210
override suspend fun cancelDownload(fileName: String): Boolean =
211211
withContext(Dispatchers.IO) {
212212
var cancelled = false
213-
var deleted = false
214213

215214
val downloadId = activeFileNames[fileName]
216215
if (downloadId != null) {
@@ -222,13 +221,16 @@ class AndroidDownloader(
222221
activeDownloads.remove(downloadId)
223222
}
224223
activeFileNames.remove(fileName)
225-
}
226224

227-
val file = File(files.appDownloadsDir(), fileName)
228-
if (file.exists()) {
229-
deleted = file.delete()
225+
// Only delete the file if we cancelled an active download (incomplete file)
226+
if (cancelled) {
227+
val file = File(files.appDownloadsDir(), fileName)
228+
if (file.exists()) {
229+
file.delete()
230+
}
231+
}
230232
}
231233

232-
cancelled || deleted
234+
cancelled
233235
}
234236
}

core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopDownloader.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,6 @@ class DesktopDownloader(
197197
override suspend fun cancelDownload(fileName: String): Boolean =
198198
withContext(Dispatchers.IO) {
199199
var cancelled = false
200-
var deleted = false
201-
202200
val downloadId = nameToId[fileName]
203201
if (downloadId != null) {
204202
activeDownloads[downloadId]?.let { call ->
@@ -209,14 +207,17 @@ class DesktopDownloader(
209207
}
210208
activeDownloads.remove(downloadId)
211209
nameToId.remove(fileName)
212-
}
213210

214-
val file = File(files.userDownloadsDir(), fileName)
215-
if (file.exists()) {
216-
deleted = file.delete()
211+
// Only delete the file if we cancelled an active download (incomplete file)
212+
if (cancelled) {
213+
val file = File(files.userDownloadsDir(), fileName)
214+
if (file.exists()) {
215+
file.delete()
216+
}
217+
}
217218
}
218219

219-
cancelled || deleted
220+
cancelled
220221
}
221222

222223
companion object {
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package zed.rainxch.core.presentation.components
22

3+
import androidx.compose.foundation.layout.Box
34
import androidx.compose.foundation.lazy.LazyListState
45
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
56
import androidx.compose.runtime.Composable
@@ -10,22 +11,21 @@ actual fun ScrollbarContainer(
1011
listState: LazyListState,
1112
enabled: Boolean,
1213
modifier: Modifier,
13-
content: `@Composable` () -> Unit,
14+
content: @Composable () -> Unit,
1415
) {
1516
Box(modifier = modifier) {
1617
content()
1718
}
1819
}
1920

20-
`@Composable`
21+
@Composable
2122
actual fun ScrollbarContainer(
2223
gridState: LazyStaggeredGridState,
2324
enabled: Boolean,
2425
modifier: Modifier,
25-
content: `@Composable` () -> Unit,
26+
content: @Composable () -> Unit,
2627
) {
2728
Box(modifier = modifier) {
2829
content()
2930
}
3031
}
31-
}

core/presentation/src/commonMain/composeResources/values/strings.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,8 +486,15 @@
486486
<string name="no_github_link_in_clipboard">No GitHub link found in clipboard</string>
487487

488488
<string name="storage">Storage</string>
489-
<string name="clear_cache">Clear cache</string>
489+
<string name="downloaded_packages">Downloaded Packages</string>
490+
<string name="downloaded_packages_description">APKs and installers from GitHub releases</string>
490491
<string name="current_size">Current size:</string>
492+
<string name="delete_all">Delete All</string>
493+
<string name="delete_downloads_confirmation_title">Delete all downloads?</string>
494+
<string name="delete_downloads_confirmation_message">This will permanently remove all downloaded APKs and installers (%1$s). You can re-download them anytime.</string>
495+
<string name="downloads_cleared">All downloaded packages have been deleted.</string>
496+
<!-- Legacy keys kept for compatibility -->
497+
<string name="clear_cache">Clear cache</string>
491498
<string name="clear">Clear</string>
492499
<string name="cache_cleared">The cache is gradually cleared.</string>
493500

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileAction.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ sealed interface ProfileAction {
3636

3737
data object OnClearCacheClick : ProfileAction
3838

39+
data object OnClearDownloadsConfirm : ProfileAction
40+
41+
data object OnClearDownloadsDismiss : ProfileAction
42+
3943
data class OnFontThemeSelected(
4044
val fontTheme: FontTheme,
4145
) : ProfileAction

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileRoot.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import zed.rainxch.core.presentation.locals.LocalBottomNavigationLiquid
3232
import zed.rainxch.core.presentation.theme.GithubStoreTheme
3333
import zed.rainxch.core.presentation.utils.ObserveAsEvents
3434
import zed.rainxch.githubstore.core.presentation.res.*
35+
import zed.rainxch.profile.presentation.components.ClearDownloadsDialog
3536
import zed.rainxch.profile.presentation.components.LogoutDialog
3637
import zed.rainxch.profile.presentation.components.sections.about
3738
import zed.rainxch.profile.presentation.components.sections.logout
@@ -83,7 +84,7 @@ fun ProfileRoot(
8384

8485
ProfileEvent.OnCacheCleared -> {
8586
coroutineScope.launch {
86-
snackbarState.showSnackbar(getString(Res.string.cache_cleared))
87+
snackbarState.showSnackbar(getString(Res.string.downloads_cleared))
8788
}
8889
}
8990

@@ -147,6 +148,18 @@ fun ProfileRoot(
147148
},
148149
)
149150
}
151+
152+
if (state.isClearDownloadsDialogVisible) {
153+
ClearDownloadsDialog(
154+
cacheSize = state.cacheSize,
155+
onDismissRequest = {
156+
viewModel.onAction(ProfileAction.OnClearDownloadsDismiss)
157+
},
158+
onConfirm = {
159+
viewModel.onAction(ProfileAction.OnClearDownloadsConfirm)
160+
},
161+
)
162+
}
150163
}
151164

152165
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ data class ProfileState(
2424
val isProxyPasswordVisible: Boolean = false,
2525
val autoDetectClipboardLinks: Boolean = true,
2626
val cacheSize: String = "",
27+
val isClearDownloadsDialogVisible: Boolean = false,
2728
val installerType: InstallerType = InstallerType.DEFAULT,
2829
val shizukuAvailability: ShizukuAvailability = ShizukuAvailability.UNAVAILABLE,
2930
val autoUpdateEnabled: Boolean = false,

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileViewModel.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ class ProfileViewModel(
300300
}
301301

302302
ProfileAction.OnClearCacheClick -> {
303+
_state.update { it.copy(isClearDownloadsDialogVisible = true) }
304+
}
305+
306+
ProfileAction.OnClearDownloadsConfirm -> {
307+
_state.update { it.copy(isClearDownloadsDialogVisible = false) }
303308
viewModelScope.launch {
304309
runCatching {
305310
profileRepository.clearCache()
@@ -309,13 +314,17 @@ class ProfileViewModel(
309314
}.onFailure { error ->
310315
_events.send(
311316
ProfileEvent.OnCacheClearError(
312-
error.message ?: "Failed to clear cache",
317+
error.message ?: "Failed to clear downloads",
313318
),
314319
)
315320
}
316321
}
317322
}
318323

324+
ProfileAction.OnClearDownloadsDismiss -> {
325+
_state.update { it.copy(isClearDownloadsDialogVisible = false) }
326+
}
327+
319328
is ProfileAction.OnThemeColorSelected -> {
320329
viewModelScope.launch {
321330
tweaksRepository.setThemeColor(action.themeColor)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package zed.rainxch.profile.presentation.components
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material3.BasicAlertDialog
11+
import androidx.compose.material3.Button
12+
import androidx.compose.material3.ButtonDefaults
13+
import androidx.compose.material3.ExperimentalMaterial3Api
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Text
16+
import androidx.compose.material3.TextButton
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.draw.clip
21+
import androidx.compose.ui.text.font.FontWeight
22+
import androidx.compose.ui.unit.dp
23+
import androidx.compose.ui.window.DialogProperties
24+
import org.jetbrains.compose.resources.stringResource
25+
import zed.rainxch.githubstore.core.presentation.res.*
26+
27+
@OptIn(ExperimentalMaterial3Api::class)
28+
@Composable
29+
fun ClearDownloadsDialog(
30+
cacheSize: String,
31+
onDismissRequest: () -> Unit,
32+
onConfirm: () -> Unit,
33+
modifier: Modifier = Modifier,
34+
) {
35+
BasicAlertDialog(
36+
onDismissRequest = onDismissRequest,
37+
properties =
38+
DialogProperties(
39+
dismissOnClickOutside = true,
40+
usePlatformDefaultWidth = false,
41+
),
42+
modifier =
43+
modifier
44+
.padding(16.dp)
45+
.clip(RoundedCornerShape(24.dp))
46+
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
47+
.padding(16.dp),
48+
) {
49+
Column(
50+
verticalArrangement = Arrangement.spacedBy(8.dp),
51+
) {
52+
Text(
53+
text = stringResource(Res.string.delete_downloads_confirmation_title),
54+
style = MaterialTheme.typography.titleLarge,
55+
color = MaterialTheme.colorScheme.onSurface,
56+
fontWeight = FontWeight.Bold,
57+
)
58+
59+
Text(
60+
text = stringResource(Res.string.delete_downloads_confirmation_message, cacheSize),
61+
style = MaterialTheme.typography.bodyLarge,
62+
color = MaterialTheme.colorScheme.onSurfaceVariant,
63+
)
64+
65+
Row(
66+
modifier = Modifier.fillMaxWidth(),
67+
verticalAlignment = Alignment.CenterVertically,
68+
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
69+
) {
70+
TextButton(
71+
onClick = onDismissRequest,
72+
) {
73+
Text(
74+
text = stringResource(Res.string.cancel),
75+
style = MaterialTheme.typography.bodyMedium,
76+
color = MaterialTheme.colorScheme.onSurface,
77+
)
78+
}
79+
80+
Button(
81+
onClick = onConfirm,
82+
colors =
83+
ButtonDefaults.buttonColors(
84+
containerColor = MaterialTheme.colorScheme.errorContainer,
85+
contentColor = MaterialTheme.colorScheme.onErrorContainer,
86+
),
87+
) {
88+
Text(
89+
text = stringResource(Res.string.delete_all),
90+
style = MaterialTheme.typography.bodyMedium,
91+
color = MaterialTheme.colorScheme.onErrorContainer,
92+
)
93+
}
94+
}
95+
}
96+
}
97+
}

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/components/sections/Others.kt

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import androidx.compose.foundation.layout.size
1313
import androidx.compose.foundation.lazy.LazyListScope
1414
import androidx.compose.foundation.shape.RoundedCornerShape
1515
import androidx.compose.material.icons.Icons
16-
import androidx.compose.material.icons.outlined.Storage
17-
import androidx.compose.material3.Button
16+
import androidx.compose.material.icons.outlined.DeleteOutline
17+
import androidx.compose.material.icons.outlined.Inventory2
1818
import androidx.compose.material3.ButtonDefaults
1919
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
20+
import androidx.compose.material3.FilledTonalButton
2021
import androidx.compose.material3.Icon
2122
import androidx.compose.material3.MaterialTheme
2223
import androidx.compose.material3.Text
@@ -51,12 +52,12 @@ fun LazyListScope.othersSection(
5152
modifier =
5253
Modifier
5354
.fillMaxWidth()
54-
.padding(horizontal = 16.dp, vertical = 8.dp),
55+
.padding(horizontal = 16.dp, vertical = 12.dp),
5556
verticalAlignment = Alignment.CenterVertically,
5657
horizontalArrangement = Arrangement.spacedBy(12.dp),
5758
) {
5859
Icon(
59-
imageVector = Icons.Outlined.Storage,
60+
imageVector = Icons.Outlined.Inventory2,
6061
contentDescription = null,
6162
modifier =
6263
Modifier
@@ -72,33 +73,40 @@ fun LazyListScope.othersSection(
7273
horizontalAlignment = Alignment.Start,
7374
) {
7475
Text(
75-
text = stringResource(Res.string.clear_cache),
76+
text = stringResource(Res.string.downloaded_packages),
7677
style = MaterialTheme.typography.titleMedium,
7778
color = MaterialTheme.colorScheme.onSurface,
7879
)
7980

81+
Text(
82+
text = stringResource(Res.string.downloaded_packages_description),
83+
style = MaterialTheme.typography.bodySmall,
84+
color = MaterialTheme.colorScheme.onSurfaceVariant,
85+
)
86+
8087
Text(
8188
text = "${stringResource(Res.string.current_size)} ${state.cacheSize}",
82-
style = MaterialTheme.typography.titleSmall,
89+
style = MaterialTheme.typography.bodySmall,
8390
color = MaterialTheme.colorScheme.onSurfaceVariant,
91+
fontWeight = FontWeight.SemiBold,
8492
)
8593
}
8694

87-
Button(
95+
FilledTonalButton(
8896
onClick = {
8997
onAction(ProfileAction.OnClearCacheClick)
9098
},
9199
shape = RoundedCornerShape(12.dp),
92100
colors =
93-
ButtonDefaults.buttonColors(
94-
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
95-
contentColor = MaterialTheme.colorScheme.onSurface,
101+
ButtonDefaults.filledTonalButtonColors(
102+
containerColor = MaterialTheme.colorScheme.errorContainer,
103+
contentColor = MaterialTheme.colorScheme.onErrorContainer,
96104
),
97105
) {
98-
Text(
99-
text = stringResource(Res.string.clear),
100-
style = MaterialTheme.typography.titleMediumEmphasized,
101-
fontWeight = FontWeight.Bold,
106+
Icon(
107+
imageVector = Icons.Outlined.DeleteOutline,
108+
contentDescription = null,
109+
modifier = Modifier.size(18.dp),
102110
)
103111
}
104112
}

0 commit comments

Comments
 (0)