fix(settings): confirm deleting all transfers

This commit is contained in:
2026-07-23 19:16:11 +02:00
parent efb3c474d1
commit 7cc0e825f6
3 changed files with 102 additions and 2 deletions

View File

@@ -48,6 +48,7 @@ fun SettingsScreen(
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
SettingsSectionContent( SettingsSectionContent(
state = state, state = state,
windowClass = windowClass,
section = state.selectedSection.takeUnless { it == SettingsSection.Overview } ?: SettingsSection.Preferences, section = state.selectedSection.takeUnless { it == SettingsSection.Overview } ?: SettingsSection.Preferences,
onBack = {}, onBack = {},
showBack = false, showBack = false,
@@ -79,6 +80,7 @@ fun SettingsScreen(
SettingsSection.Overview -> SettingsOverview(state, onSectionSelected, largeTitle = true) SettingsSection.Overview -> SettingsOverview(state, onSectionSelected, largeTitle = true)
else -> SettingsSectionContent( else -> SettingsSectionContent(
state = state, state = state,
windowClass = windowClass,
section = state.selectedSection, section = state.selectedSection,
onBack = { onBack = {
onSectionSelected( onSectionSelected(
@@ -118,6 +120,7 @@ fun SettingsScreen(
@Composable @Composable
private fun SettingsSectionContent( private fun SettingsSectionContent(
state: SettingsState, state: SettingsState,
windowClass: WindowClass,
section: SettingsSection, section: SettingsSection,
onBack: () -> Unit, onBack: () -> Unit,
showBack: Boolean, showBack: Boolean,
@@ -157,7 +160,7 @@ private fun SettingsSectionContent(
showBack = showBack, showBack = showBack,
) )
SettingsSection.Notifications -> NotificationSettings(state, onNotificationsChanged, onOpenNotificationSettings, onBack, showBack) SettingsSection.Notifications -> NotificationSettings(state, onNotificationsChanged, onOpenNotificationSettings, onBack, showBack)
SettingsSection.Storage -> StorageSettings(state, onDeleteAllTransfers, onBack, showBack) SettingsSection.Storage -> StorageSettings(state, windowClass, onDeleteAllTransfers, onBack, showBack)
SettingsSection.About -> AboutSettings( SettingsSection.About -> AboutSettings(
state = state, state = state,
onDiagnosticsChanged = onDiagnosticsChanged, onDiagnosticsChanged = onDiagnosticsChanged,

View File

@@ -13,17 +13,28 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vnidrop.app.ui.components.AdaptiveDrawer
import com.vnidrop.app.ui.components.DestructiveButton
import com.vnidrop.app.ui.components.SecondaryButton
import com.vnidrop.app.ui.state.formatBytes import com.vnidrop.app.ui.state.formatBytes
import com.vnidrop.app.ui.state.WindowClass
import com.vnidrop.app.ui.theme.LocalVniDropColors import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.button_cancel
import vnidrop.shared.generated.resources.storage_app_data import vnidrop.shared.generated.resources.storage_app_data
import vnidrop.shared.generated.resources.storage_calculating import vnidrop.shared.generated.resources.storage_calculating
import vnidrop.shared.generated.resources.storage_delete_transfers import vnidrop.shared.generated.resources.storage_delete_transfers
import vnidrop.shared.generated.resources.storage_delete_transfers_description
import vnidrop.shared.generated.resources.storage_deleting import vnidrop.shared.generated.resources.storage_deleting
import vnidrop.shared.generated.resources.storage_total import vnidrop.shared.generated.resources.storage_total
import vnidrop.shared.generated.resources.storage_received_files import vnidrop.shared.generated.resources.storage_received_files
@@ -35,10 +46,12 @@ import vnidrop.shared.generated.resources.storage_footer
@Composable @Composable
internal fun StorageSettings( internal fun StorageSettings(
state: SettingsState, state: SettingsState,
windowClass: WindowClass,
onDeleteAllTransfers: () -> Unit, onDeleteAllTransfers: () -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
showBack: Boolean, showBack: Boolean,
) { ) {
var showDeleteConfirmation by rememberSaveable { mutableStateOf(false) }
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
SettingsTopBar(stringResource(Res.string.storage_title), onBack, showBack) SettingsTopBar(stringResource(Res.string.storage_title), onBack, showBack)
val storage = state.storage val storage = state.storage
@@ -67,7 +80,7 @@ internal fun StorageSettings(
} }
} }
Button( Button(
onClick = onDeleteAllTransfers, onClick = { showDeleteConfirmation = true },
enabled = !state.isDeletingTransfers, enabled = !state.isDeletingTransfers,
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
) { ) {
@@ -79,6 +92,50 @@ internal fun StorageSettings(
color = LocalVniDropColors.current.foregroundLighter, color = LocalVniDropColors.current.foregroundLighter,
) )
} }
if (showDeleteConfirmation) {
AdaptiveDrawer(
windowClass = windowClass,
onDismissRequest = { showDeleteConfirmation = false },
) {
DeleteAllTransfersPanel(
onCancel = { showDeleteConfirmation = false },
onConfirm = {
showDeleteConfirmation = false
onDeleteAllTransfers()
},
)
}
}
}
@Composable
private fun DeleteAllTransfersPanel(
onCancel: () -> Unit,
onConfirm: () -> Unit,
) {
Column(
Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 14.dp),
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
Text(
stringResource(Res.string.storage_delete_transfers),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
)
Text(
stringResource(Res.string.storage_delete_transfers_description),
color = LocalVniDropColors.current.foregroundLighter,
style = MaterialTheme.typography.bodyMedium,
)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End)) {
SecondaryButton(stringResource(Res.string.button_cancel), onClick = onCancel)
DestructiveButton(
stringResource(Res.string.storage_delete_transfers),
onClick = onConfirm,
modifier = Modifier.testTag("confirm-delete-all-transfers"),
)
}
}
} }
@Composable @Composable

View File

@@ -64,6 +64,7 @@ import com.vnidrop.app.ui.theme.VniDropTheme
import com.vnidrop.app.ui.theme.LocalVniDropColors import com.vnidrop.app.ui.theme.LocalVniDropColors
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
@@ -172,6 +173,45 @@ class FoundationComposeTest {
runOnIdle { assertTrue(applied) } runOnIdle { assertTrue(applied) }
} }
@Test
fun storageDeleteAllTransfersRequiresConfirmation() = runComposeUiTest {
var deleteRequested = false
setContent {
VniDropTheme(isDarkTheme = false) {
SettingsScreen(
state = SettingsState(selectedSection = SettingsSection.Storage),
windowClass = WindowClass.Desktop,
onSectionSelected = {},
onUsernameChanged = {},
onThemeModeChanged = {},
onChooseFolder = {},
onResetFolder = {},
onNotificationsChanged = {},
onOpenNotificationSettings = {},
onDiagnosticsChanged = {},
onBugWhatChanged = {},
onBugExpectedChanged = {},
onBugStepsChanged = {},
onBugContactChanged = {},
onBugIncludeLogsChanged = {},
onSubmitBugReport = {},
onDeleteAllTransfers = { deleteRequested = true },
)
}
}
onNodeWithText("Delete all transfers").performClick()
onNodeWithText(
"This clears all sent and received transfer records from your history. Your received files are not deleted. " +
"Cached shared content that is no longer needed is reclaimed automatically, which may take a little time. " +
"This cant be undone.",
).assertIsDisplayed()
runOnIdle { assertFalse(deleteRequested) }
onNodeWithTag("confirm-delete-all-transfers").performClick()
runOnIdle { assertTrue(deleteRequested) }
}
@Test @Test
fun aboutSettingsShowsTheSharedProductAndPrivacyContent() = runComposeUiTest { fun aboutSettingsShowsTheSharedProductAndPrivacyContent() = runComposeUiTest {
setContent { setContent {