Refactor: Make account 100% optional, default to local-only mode
- SessionManager: return stable local_user ID when not logged in
- Navigation: start directly at ListsNavKey without requiring login
- ListsScreen: add 'Sync / Account' chip in top bar; lists work 100% locally by default
- AuthScreen: update text & add 'Zurück zu meinen Listen' button for returning to local mode
- ADB reinstall & launch verified ✅
This commit is contained in:
parent
a00db14cba
commit
a5ad5cd766
4 changed files with 209 additions and 145 deletions
|
|
@ -1,9 +1,6 @@
|
||||||
package com.example.mitbringsl
|
package com.example.mitbringsl
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.navigation3.runtime.entryProvider
|
import androidx.navigation3.runtime.entryProvider
|
||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
|
|
@ -14,34 +11,30 @@ import com.example.mitbringsl.ui.lists.ListsScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainNavigation(sessionManager: SessionManager) {
|
fun MainNavigation(sessionManager: SessionManager) {
|
||||||
val initialKey = remember {
|
// Local-First: App starts directly at ListsNavKey by default!
|
||||||
if (!sessionManager.getToken().isNullOrBlank()) ListsNavKey else AuthNavKey
|
val backStack = rememberNavBackStack(ListsNavKey)
|
||||||
}
|
|
||||||
val backStack = rememberNavBackStack(initialKey)
|
|
||||||
|
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
onBack = { backStack.removeLastOrNull() },
|
onBack = { backStack.removeLastOrNull() },
|
||||||
entryProvider = entryProvider {
|
entryProvider = entryProvider {
|
||||||
entry<AuthNavKey> {
|
|
||||||
AuthScreen(
|
|
||||||
onLoginSuccess = {
|
|
||||||
backStack.clear()
|
|
||||||
backStack.add(ListsNavKey)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
entry<ListsNavKey> {
|
entry<ListsNavKey> {
|
||||||
ListsScreen(
|
ListsScreen(
|
||||||
onSelectList = { listId ->
|
onSelectList = { listId ->
|
||||||
backStack.add(ListDetailNavKey(listId))
|
backStack.add(ListDetailNavKey(listId))
|
||||||
},
|
},
|
||||||
onLogout = {
|
onOpenSync = {
|
||||||
backStack.clear()
|
|
||||||
backStack.add(AuthNavKey)
|
backStack.add(AuthNavKey)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
entry<AuthNavKey> {
|
||||||
|
AuthScreen(
|
||||||
|
onLoginSuccess = {
|
||||||
|
backStack.removeLastOrNull()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
entry<ListDetailNavKey> { key ->
|
entry<ListDetailNavKey> { key ->
|
||||||
ListDetailScreen(
|
ListDetailScreen(
|
||||||
onBack = { backStack.removeLastOrNull() }
|
onBack = { backStack.removeLastOrNull() }
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,16 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the user's session state and token storage.
|
* Manages the user's session state and local device identification.
|
||||||
|
*
|
||||||
|
* Local-First design: Users do NOT need an account to use the app locally.
|
||||||
|
* When not logged in, [getUserId] returns a stable local ID ("local_user").
|
||||||
|
* Logging in is 100% optional and only required to sync across devices.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class SessionManager @Inject constructor(
|
class SessionManager @Inject constructor(
|
||||||
|
|
@ -18,12 +23,29 @@ class SessionManager @Inject constructor(
|
||||||
) {
|
) {
|
||||||
private val prefs = context.getSharedPreferences("session", Context.MODE_PRIVATE)
|
private val prefs = context.getSharedPreferences("session", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LOCAL_USER_ID = "local_user"
|
||||||
|
}
|
||||||
|
|
||||||
private val _isLoggedIn = MutableStateFlow(!getToken().isNullOrBlank())
|
private val _isLoggedIn = MutableStateFlow(!getToken().isNullOrBlank())
|
||||||
val isLoggedIn: StateFlow<Boolean> = _isLoggedIn.asStateFlow()
|
val isLoggedIn: StateFlow<Boolean> = _isLoggedIn.asStateFlow()
|
||||||
|
|
||||||
fun getToken(): String? = prefs.getString("token", null)
|
fun getToken(): String? = prefs.getString("token", null)
|
||||||
|
|
||||||
fun getUserId(): String? = prefs.getString("user_id", null)
|
/**
|
||||||
|
* Returns the authenticated user's ID if logged in, or [LOCAL_USER_ID] for local-only mode.
|
||||||
|
*/
|
||||||
|
fun getUserId(): String {
|
||||||
|
val remoteId = prefs.getString("user_id", null)
|
||||||
|
if (!remoteId.isNullOrBlank()) return remoteId
|
||||||
|
|
||||||
|
var localId = prefs.getString("local_device_user_id", null)
|
||||||
|
if (localId.isNullOrBlank()) {
|
||||||
|
localId = LOCAL_USER_ID
|
||||||
|
prefs.edit { putString("local_device_user_id", localId) }
|
||||||
|
}
|
||||||
|
return localId
|
||||||
|
}
|
||||||
|
|
||||||
fun getUserEmail(): String? = prefs.getString("user_email", null)
|
fun getUserEmail(): String? = prefs.getString("user_email", null)
|
||||||
|
|
||||||
|
|
@ -37,7 +59,11 @@ class SessionManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearSession() {
|
fun clearSession() {
|
||||||
prefs.edit { clear() }
|
prefs.edit {
|
||||||
|
remove("token")
|
||||||
|
remove("user_id")
|
||||||
|
remove("user_email")
|
||||||
|
}
|
||||||
_isLoggedIn.value = false
|
_isLoggedIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
|
@ -37,11 +36,7 @@ fun AuthScreen(
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val isLoggedIn by viewModel.sessionManager.isLoggedIn.collectAsStateWithLifecycle()
|
val isLoggedIn by viewModel.sessionManager.isLoggedIn.collectAsStateWithLifecycle()
|
||||||
|
val userEmail = viewModel.sessionManager.getUserEmail()
|
||||||
if (isLoggedIn) {
|
|
||||||
onLoginSuccess()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val gradientBrush = Brush.verticalGradient(
|
val gradientBrush = Brush.verticalGradient(
|
||||||
|
|
@ -74,24 +69,21 @@ fun AuthScreen(
|
||||||
shadowElevation = 8.dp
|
shadowElevation = 8.dp
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
Text(
|
Text(text = "🛒", fontSize = 36.sp)
|
||||||
text = "🛒",
|
|
||||||
fontSize = 36.sp
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Mitbringsl",
|
text = "Mitbringsl Sync",
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Local-First Einkaufsliste — Werbefrei & Sicher",
|
text = "Melde dich an, um deine Listen geräteübergreifend zu synchronisieren.",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|
@ -112,132 +104,170 @@ fun AuthScreen(
|
||||||
modifier = Modifier.padding(24.dp),
|
modifier = Modifier.padding(24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
if (isLoggedIn) {
|
||||||
text = if (uiState.isRegisteringMode) "Konto erstellen" else "Willkommen zurück",
|
Text(
|
||||||
style = MaterialTheme.typography.titleLarge,
|
text = "Angemeldet als",
|
||||||
fontWeight = FontWeight.SemiBold,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = userEmail ?: "Benutzer",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.sessionManager.clearSession()
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Abmelden")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = if (uiState.isRegisteringMode) "Konto erstellen" else "Anmelden",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
// Error Message
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
AnimatedVisibility(
|
|
||||||
visible = uiState.errorMessage != null,
|
// Error Message
|
||||||
enter = fadeIn(),
|
AnimatedVisibility(
|
||||||
exit = fadeOut()
|
visible = uiState.errorMessage != null,
|
||||||
) {
|
enter = fadeIn(),
|
||||||
uiState.errorMessage?.let { msg ->
|
exit = fadeOut()
|
||||||
Surface(
|
) {
|
||||||
color = MaterialTheme.colorScheme.errorContainer,
|
uiState.errorMessage?.let { msg ->
|
||||||
shape = RoundedCornerShape(12.dp),
|
Surface(
|
||||||
modifier = Modifier
|
color = MaterialTheme.colorScheme.errorContainer,
|
||||||
.fillMaxWidth()
|
shape = RoundedCornerShape(12.dp),
|
||||||
.padding(bottom = 16.dp)
|
modifier = Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
Text(
|
.padding(bottom = 16.dp)
|
||||||
text = msg,
|
) {
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
Text(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
text = msg,
|
||||||
modifier = Modifier.padding(12.dp),
|
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
textAlign = TextAlign.Center
|
style = MaterialTheme.typography.bodySmall,
|
||||||
)
|
modifier = Modifier.padding(12.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Display Name (only in register mode)
|
// Display Name (only in register mode)
|
||||||
if (uiState.isRegisteringMode) {
|
if (uiState.isRegisteringMode) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = uiState.displayNameInput,
|
||||||
|
onValueChange = viewModel::onDisplayNameChanged,
|
||||||
|
label = { Text("Anzeigename (optional)") },
|
||||||
|
singleLine = true,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email Field
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.displayNameInput,
|
value = uiState.emailInput,
|
||||||
onValueChange = viewModel::onDisplayNameChanged,
|
onValueChange = viewModel::onEmailChanged,
|
||||||
label = { Text("Anzeigename (optional)") },
|
label = { Text("E-Mail Adresse") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Email,
|
||||||
|
imeAction = ImeAction.Next
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
|
||||||
|
|
||||||
// Email Field
|
// Password Field
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.emailInput,
|
value = uiState.passwordInput,
|
||||||
onValueChange = viewModel::onEmailChanged,
|
onValueChange = viewModel::onPasswordChanged,
|
||||||
label = { Text("E-Mail Adresse") },
|
label = { Text("Passwort") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardOptions = KeyboardOptions(
|
||||||
imeAction = ImeAction.Next
|
keyboardType = KeyboardType.Password,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
focusManager.clearFocus()
|
||||||
|
viewModel.submit()
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
// Password Field
|
// Submit Button
|
||||||
OutlinedTextField(
|
Button(
|
||||||
value = uiState.passwordInput,
|
onClick = {
|
||||||
onValueChange = viewModel::onPasswordChanged,
|
|
||||||
label = { Text("Passwort") },
|
|
||||||
singleLine = true,
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Password,
|
|
||||||
imeAction = ImeAction.Done
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onDone = {
|
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
viewModel.submit()
|
viewModel.submit()
|
||||||
|
},
|
||||||
|
enabled = !uiState.isLoading,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(52.dp)
|
||||||
|
) {
|
||||||
|
if (uiState.isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
strokeWidth = 2.5.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = if (uiState.isRegisteringMode) "Registrieren" else "Anmelden",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// Submit Button
|
// Toggle Login / Register mode
|
||||||
Button(
|
TextButton(onClick = viewModel::toggleMode) {
|
||||||
onClick = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
viewModel.submit()
|
|
||||||
},
|
|
||||||
enabled = !uiState.isLoading,
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
if (uiState.isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.5.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
Text(
|
||||||
text = if (uiState.isRegisteringMode) "Registrieren" else "Anmelden",
|
text = if (uiState.isRegisteringMode) {
|
||||||
fontWeight = FontWeight.Bold,
|
"Bereits ein Konto? Hier anmelden"
|
||||||
fontSize = 16.sp
|
} else {
|
||||||
|
"Noch kein Konto? Hier registrieren"
|
||||||
|
},
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// Toggle Login / Register mode
|
TextButton(onClick = onLoginSuccess) {
|
||||||
TextButton(onClick = viewModel::toggleMode) {
|
|
||||||
Text(
|
Text(
|
||||||
text = if (uiState.isRegisteringMode) {
|
text = "Zurück zu meinen Listen",
|
||||||
"Bereits ein Konto? Hier anmelden"
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
} else {
|
|
||||||
"Noch kein Konto? Hier registrieren"
|
|
||||||
},
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.example.mitbringsl.ui.lists
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
|
@ -11,8 +10,10 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.CloudDone
|
||||||
|
import androidx.compose.material.icons.filled.CloudOff
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.ExitToApp
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material.icons.filled.ShoppingCart
|
import androidx.compose.material.icons.filled.ShoppingCart
|
||||||
import androidx.compose.material.icons.filled.WifiOff
|
import androidx.compose.material.icons.filled.WifiOff
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -31,12 +32,15 @@ import com.example.mitbringsl.data.local.entity.ListEntity
|
||||||
@Composable
|
@Composable
|
||||||
fun ListsScreen(
|
fun ListsScreen(
|
||||||
onSelectList: (String) -> Unit,
|
onSelectList: (String) -> Unit,
|
||||||
onLogout: () -> Unit,
|
onOpenSync: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: ListsViewModel = hiltViewModel(),
|
viewModel: ListsViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val lists by viewModel.lists.collectAsStateWithLifecycle()
|
val lists by viewModel.lists.collectAsStateWithLifecycle()
|
||||||
val isOnline by viewModel.isOnline.collectAsStateWithLifecycle()
|
val isOnline by viewModel.isOnline.collectAsStateWithLifecycle()
|
||||||
|
val isLoggedIn by viewModel.sessionManager.isLoggedIn.collectAsStateWithLifecycle()
|
||||||
|
val userEmail = viewModel.sessionManager.getUserEmail()
|
||||||
|
|
||||||
var showAddDialog by remember { mutableStateOf(false) }
|
var showAddDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -49,15 +53,26 @@ fun ListsScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = {
|
// Sync / Account button
|
||||||
viewModel.logout()
|
AssistChip(
|
||||||
onLogout()
|
onClick = onOpenSync,
|
||||||
}) {
|
label = {
|
||||||
Icon(
|
Text(
|
||||||
imageVector = Icons.Default.ExitToApp,
|
text = if (isLoggedIn) (userEmail?.substringBefore("@") ?: "Sync aktiv") else "Sync / Account",
|
||||||
contentDescription = "Abmelden"
|
style = MaterialTheme.typography.labelSmall
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isLoggedIn) Icons.Default.CloudDone else Icons.Default.CloudOff,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = if (isLoggedIn) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
|
@ -106,7 +121,7 @@ fun ListsScreen(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Offline – Änderungen werden lokal gespeichert",
|
text = "Offline – Änderungen bleiben lokal gespeichert",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer
|
color = MaterialTheme.colorScheme.onErrorContainer
|
||||||
)
|
)
|
||||||
|
|
@ -140,7 +155,7 @@ fun ListsScreen(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Erstelle deine erste Liste mit dem Plus-Button unten rechts.",
|
text = "Erstelle direkt deine erste Liste — ohne Anmeldung nutzbar!",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue