feat(auth): show delete-local-data dialog on logout for logged-in users

This commit is contained in:
Jens Reinemann 2026-05-19 00:10:17 +02:00
parent 320f0e8880
commit 557a4bcaf8
4 changed files with 47 additions and 4 deletions

View file

@ -324,13 +324,33 @@ internal fun SettingsScreen(
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
OutlinedButton( OutlinedButton(
onClick = viewModel::logout, onClick = viewModel::onLogoutClicked,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Abmelden") Text("Abmelden")
} }
} }
} }
if (uiState.showLogoutDialog) {
AlertDialog(
onDismissRequest = viewModel::onLogoutDialogDismissed,
title = { Text("Lokale Daten löschen?") },
text = {
Text("Möchtest du alle lokal gespeicherten Daten (Inventar, Nachrichten, …) von diesem Gerät entfernen?")
},
confirmButton = {
TextButton(onClick = { viewModel.logout(deleteLocalData = true) }) {
Text("Löschen")
}
},
dismissButton = {
TextButton(onClick = { viewModel.logout(deleteLocalData = false) }) {
Text("Behalten")
}
}
)
}
} else { } else {
OutlinedTextField( OutlinedTextField(
value = uiState.loginUsername, value = uiState.loginUsername,

View file

@ -27,7 +27,8 @@ internal data class SettingsUiState(
val pendingQueueCount: Int = 0, val pendingQueueCount: Int = 0,
val isSyncing: Boolean = false, val isSyncing: Boolean = false,
val lastSyncTime: String? = null, val lastSyncTime: String? = null,
val openAiApiKey: String = "" val openAiApiKey: String = "",
val showLogoutDialog: Boolean = false
) )
internal sealed interface ImportResult { internal sealed interface ImportResult {

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.bollwerk.app.data.db.BollwerkDatabase
import de.bollwerk.app.data.db.dao.PendingSyncOpDao import de.bollwerk.app.data.db.dao.PendingSyncOpDao
import de.bollwerk.app.data.sync.ConnectionState import de.bollwerk.app.data.sync.ConnectionState
import de.bollwerk.app.data.sync.WebSocketClient import de.bollwerk.app.data.sync.WebSocketClient
@ -22,6 +23,7 @@ import de.bollwerk.app.domain.repository.ImportExportRepository
import de.bollwerk.app.domain.repository.SettingsRepository import de.bollwerk.app.domain.repository.SettingsRepository
import de.bollwerk.app.domain.repository.SyncService import de.bollwerk.app.domain.repository.SyncService
import de.bollwerk.app.notification.MessagingService import de.bollwerk.app.notification.MessagingService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -29,6 +31,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
@ -43,6 +46,7 @@ internal class SettingsViewModel @Inject constructor(
private val syncService: SyncService, private val syncService: SyncService,
private val webSocketClient: WebSocketClient, private val webSocketClient: WebSocketClient,
private val pendingSyncOpDao: PendingSyncOpDao, private val pendingSyncOpDao: PendingSyncOpDao,
private val database: BollwerkDatabase,
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
@ -227,8 +231,21 @@ internal class SettingsViewModel @Inject constructor(
} }
} }
fun logout() { fun onLogoutClicked() {
_uiState.update { it.copy(showLogoutDialog = true) }
}
fun onLogoutDialogDismissed() {
_uiState.update { it.copy(showLogoutDialog = false) }
}
fun logout(deleteLocalData: Boolean = false) {
viewModelScope.launch { viewModelScope.launch {
if (deleteLocalData) {
withContext(Dispatchers.IO) {
database.clearAllTables()
}
}
syncService.logout() syncService.logout()
webSocketClient.disconnect() webSocketClient.disconnect()
MessagingService.stop(context) MessagingService.stop(context)
@ -237,7 +254,8 @@ internal class SettingsViewModel @Inject constructor(
isLoggedIn = false, isLoggedIn = false,
loggedInUsername = "", loggedInUsername = "",
connectionState = ConnectionState.NotConfigured, connectionState = ConnectionState.NotConfigured,
syncActivity = null syncActivity = null,
showLogoutDialog = false
) )
} }
} }

View file

@ -4,6 +4,7 @@ import android.content.Context
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import android.content.ContentResolver import android.content.ContentResolver
import android.net.Uri import android.net.Uri
import de.bollwerk.app.data.db.BollwerkDatabase
import de.bollwerk.app.data.db.dao.PendingSyncOpDao import de.bollwerk.app.data.db.dao.PendingSyncOpDao
import de.bollwerk.app.data.db.entity.PendingSyncOpEntity import de.bollwerk.app.data.db.entity.PendingSyncOpEntity
import de.bollwerk.app.data.sync.WebSocketClient import de.bollwerk.app.data.sync.WebSocketClient
@ -53,6 +54,7 @@ class SettingsViewModelTest {
private lateinit var fakeSyncService: FakeSyncService private lateinit var fakeSyncService: FakeSyncService
private lateinit var fakeWebSocketClient: FakeWebSocketClient private lateinit var fakeWebSocketClient: FakeWebSocketClient
private lateinit var fakePendingSyncOpDao: FakePendingSyncOpDao private lateinit var fakePendingSyncOpDao: FakePendingSyncOpDao
private lateinit var mockDatabase: BollwerkDatabase
private lateinit var mockContext: Context private lateinit var mockContext: Context
private lateinit var tempDir: File private lateinit var tempDir: File
private lateinit var viewModel: SettingsViewModel private lateinit var viewModel: SettingsViewModel
@ -65,6 +67,7 @@ class SettingsViewModelTest {
fakeSyncService = FakeSyncService() fakeSyncService = FakeSyncService()
fakeWebSocketClient = FakeWebSocketClient() fakeWebSocketClient = FakeWebSocketClient()
fakePendingSyncOpDao = FakePendingSyncOpDao() fakePendingSyncOpDao = FakePendingSyncOpDao()
mockDatabase = mockk(relaxed = true)
tempDir = File(System.getProperty("java.io.tmpdir"), "test_exports") tempDir = File(System.getProperty("java.io.tmpdir"), "test_exports")
tempDir.mkdirs() tempDir.mkdirs()
mockContext = mockk(relaxed = true) { mockContext = mockk(relaxed = true) {
@ -85,6 +88,7 @@ class SettingsViewModelTest {
syncService = fakeSyncService, syncService = fakeSyncService,
webSocketClient = fakeWebSocketClient, webSocketClient = fakeWebSocketClient,
pendingSyncOpDao = fakePendingSyncOpDao, pendingSyncOpDao = fakePendingSyncOpDao,
database = mockDatabase,
context = mockContext context = mockContext
) )