holy moly thats a lotta damage
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
package com.shieldai.android.ui.screens.services
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.shieldai.android.R
|
||||
import com.shieldai.android.ui.components.BadgeVariant
|
||||
import com.shieldai.android.ui.components.ShieldBadge
|
||||
import com.shieldai.android.ui.components.ShieldButton
|
||||
import com.shieldai.android.ui.components.ShieldButtonVariant
|
||||
import com.shieldai.android.ui.components.ShieldCard
|
||||
import com.shieldai.android.ui.components.ShieldEmptyState
|
||||
import com.shieldai.android.ui.components.ShieldTextField
|
||||
import com.shieldai.android.viewmodel.HomeTitleViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeTitleScreen(
|
||||
onBack: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: HomeTitleViewModel = viewModel(factory = HomeTitleViewModel.Factory)
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var showAddSheet by remember { mutableStateOf(false) }
|
||||
var newAddress by remember { mutableStateOf("") }
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = { Text("HomeTitle", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onBack) { Text("Back") }
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (!showAddSheet) {
|
||||
FloatingActionButton(onClick = { showAddSheet = true }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_dashboard),
|
||||
contentDescription = "Add property"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
when {
|
||||
uiState.isLoading && uiState.properties.isEmpty() -> {
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
contentAlignment = androidx.compose.ui.Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
uiState.properties.isEmpty() -> {
|
||||
ShieldEmptyState(
|
||||
title = "No properties",
|
||||
description = "Add properties to monitor for title fraud",
|
||||
actionButton = {
|
||||
ShieldButton(
|
||||
text = "Add Property",
|
||||
onClick = { showAddSheet = true },
|
||||
variant = ShieldButtonVariant.Primary
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
HomeTitleContent(
|
||||
uiState = uiState,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showAddSheet) {
|
||||
AddPropertySheet(
|
||||
onDismiss = {
|
||||
showAddSheet = false
|
||||
newAddress = ""
|
||||
},
|
||||
onAdd = {
|
||||
viewModel.addProperty(newAddress)
|
||||
showAddSheet = false
|
||||
newAddress = ""
|
||||
},
|
||||
address = newAddress,
|
||||
onAddressChange = { newAddress = it },
|
||||
isLoading = uiState.isAdding
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomeTitleContent(
|
||||
uiState: HomeTitleViewModel.HomeTitleUiState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = "Properties (${uiState.properties.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
items(uiState.properties) { property ->
|
||||
PropertyCard(property)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PropertyCard(property: com.shieldai.android.data.model.Property) {
|
||||
ShieldCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = property.address,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
property.ownerName?.let {
|
||||
Text(
|
||||
text = "Owner: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
ShieldBadge(
|
||||
text = property.type,
|
||||
variant = BadgeVariant.Info
|
||||
)
|
||||
property.county?.let {
|
||||
ShieldBadge(
|
||||
text = it,
|
||||
variant = BadgeVariant.Default
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ShieldBadge(
|
||||
text = property.status,
|
||||
variant = if (property.status == "monitored") BadgeVariant.Success
|
||||
else BadgeVariant.Default
|
||||
)
|
||||
}
|
||||
property.updatedAt?.let {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Updated: $it",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AddPropertySheet(
|
||||
onDismiss: () -> Unit,
|
||||
onAdd: () -> Unit,
|
||||
address: String,
|
||||
onAddressChange: (String) -> Unit,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Add Property",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
ShieldTextField(
|
||||
value = address,
|
||||
onValueChange = onAddressChange,
|
||||
label = "Property address",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
ShieldButton(
|
||||
text = "Cancel",
|
||||
onClick = onDismiss,
|
||||
variant = ShieldButtonVariant.Secondary,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ShieldButton(
|
||||
text = "Add",
|
||||
onClick = onAdd,
|
||||
variant = ShieldButtonVariant.Primary,
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = address.isNotBlank(),
|
||||
loading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package com.shieldai.android.ui.screens.services
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.shieldai.android.R
|
||||
import com.shieldai.android.ui.components.BadgeVariant
|
||||
import com.shieldai.android.ui.components.ShieldBadge
|
||||
import com.shieldai.android.ui.components.ShieldButton
|
||||
import com.shieldai.android.ui.components.ShieldButtonVariant
|
||||
import com.shieldai.android.ui.components.ShieldCard
|
||||
import com.shieldai.android.ui.components.ShieldEmptyState
|
||||
import com.shieldai.android.ui.components.ShieldTextField
|
||||
import com.shieldai.android.viewmodel.RemoveBrokersViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RemoveBrokersScreen(
|
||||
onBack: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: RemoveBrokersViewModel = viewModel(factory = RemoveBrokersViewModel.Factory)
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var showCreateSheet by remember { mutableStateOf(false) }
|
||||
var selectedListingId by remember { mutableStateOf("") }
|
||||
var selectedListingName by remember { mutableStateOf("") }
|
||||
var notes by remember { mutableStateOf("") }
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = { Text("RemoveBrokers", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onBack) { Text("Back") }
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (!showCreateSheet) {
|
||||
FloatingActionButton(onClick = { showCreateSheet = true }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_dashboard),
|
||||
contentDescription = "Start removal"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
when {
|
||||
uiState.isLoading && uiState.listings.isEmpty() -> {
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
contentAlignment = androidx.compose.ui.Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
uiState.listings.isEmpty() && uiState.removalRequests.isEmpty() -> {
|
||||
ShieldEmptyState(
|
||||
title = "No listings",
|
||||
description = "No broker listings found. Start a removal request to get started.",
|
||||
actionButton = {
|
||||
ShieldButton(
|
||||
text = "Start Removal",
|
||||
onClick = { showCreateSheet = true },
|
||||
variant = ShieldButtonVariant.Primary
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
RemoveBrokersContent(
|
||||
uiState = uiState,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showCreateSheet) {
|
||||
CreateRemovalSheet(
|
||||
onDismiss = {
|
||||
showCreateSheet = false
|
||||
selectedListingId = ""
|
||||
selectedListingName = ""
|
||||
notes = ""
|
||||
},
|
||||
onCreate = {
|
||||
viewModel.createRemovalRequest(selectedListingId, notes.ifBlank { null })
|
||||
showCreateSheet = false
|
||||
selectedListingId = ""
|
||||
selectedListingName = ""
|
||||
notes = ""
|
||||
},
|
||||
listingName = selectedListingName,
|
||||
onListingNameChange = { selectedListingName = it },
|
||||
notes = notes,
|
||||
onNotesChange = { notes = it },
|
||||
isLoading = uiState.isCreating
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RemoveBrokersContent(
|
||||
uiState: RemoveBrokersViewModel.RemoveBrokersUiState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
if (uiState.listings.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Broker Listings (${uiState.listings.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
items(uiState.listings) { listing ->
|
||||
ListingCard(listing)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.removalRequests.isNotEmpty()) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Removal Requests (${uiState.removalRequests.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
items(uiState.removalRequests) { request ->
|
||||
RemovalRequestCard(request)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ListingCard(listing: com.shieldai.android.data.model.BrokerListing) {
|
||||
ShieldCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = listing.brokerName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ShieldBadge(
|
||||
text = listing.status,
|
||||
variant = if (listing.status == "active") BadgeVariant.Warning
|
||||
else BadgeVariant.Default
|
||||
)
|
||||
}
|
||||
listing.propertyAddress?.let {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
listing.dateFound?.let {
|
||||
Text(
|
||||
text = "Found: $it",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RemovalRequestCard(request: com.shieldai.android.data.model.RemovalRequest) {
|
||||
ShieldCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Request #${request.id.take(8)}",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ShieldBadge(
|
||||
text = request.status,
|
||||
variant = when (request.status.lowercase()) {
|
||||
"completed" -> BadgeVariant.Success
|
||||
"pending" -> BadgeVariant.Warning
|
||||
"in_progress" -> BadgeVariant.Info
|
||||
else -> BadgeVariant.Default
|
||||
}
|
||||
)
|
||||
}
|
||||
request.submittedDate?.let {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Submitted: $it",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
request.notes?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CreateRemovalSheet(
|
||||
onDismiss: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
listingName: String,
|
||||
onListingNameChange: (String) -> Unit,
|
||||
notes: String,
|
||||
onNotesChange: (String) -> Unit,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Start Removal Request",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
ShieldTextField(
|
||||
value = listingName,
|
||||
onValueChange = onListingNameChange,
|
||||
label = "Broker / Listing name",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
ShieldTextField(
|
||||
value = notes,
|
||||
onValueChange = onNotesChange,
|
||||
label = "Notes (optional)",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
ShieldButton(
|
||||
text = "Cancel",
|
||||
onClick = onDismiss,
|
||||
variant = ShieldButtonVariant.Secondary,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ShieldButton(
|
||||
text = "Submit",
|
||||
onClick = onCreate,
|
||||
variant = ShieldButtonVariant.Primary,
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = listingName.isNotBlank(),
|
||||
loading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package com.shieldai.android.ui.screens.services
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.shieldai.android.R
|
||||
import com.shieldai.android.ui.components.ShieldBadge
|
||||
import com.shieldai.android.ui.components.ShieldButton
|
||||
import com.shieldai.android.ui.components.ShieldButtonVariant
|
||||
import com.shieldai.android.ui.components.ShieldCard
|
||||
import com.shieldai.android.ui.components.ShieldEmptyState
|
||||
import com.shieldai.android.ui.components.ShieldTextField
|
||||
import com.shieldai.android.viewmodel.SpamShieldViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SpamShieldScreen(
|
||||
onBack: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SpamShieldViewModel = viewModel(factory = SpamShieldViewModel.Factory)
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var showCreateSheet by remember { mutableStateOf(false) }
|
||||
var newPattern by remember { mutableStateOf("") }
|
||||
var newAction by remember { mutableStateOf("block") }
|
||||
var newDescription by remember { mutableStateOf("") }
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = { Text("SpamShield", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onBack) { Text("Back") }
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (!showCreateSheet) {
|
||||
FloatingActionButton(onClick = { showCreateSheet = true }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_dashboard),
|
||||
contentDescription = "Create rule"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
when {
|
||||
uiState.isLoading && uiState.rules.isEmpty() -> {
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
contentAlignment = androidx.compose.ui.Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
SpamShieldContent(
|
||||
uiState = uiState,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showCreateSheet) {
|
||||
CreateRuleSheet(
|
||||
onDismiss = {
|
||||
showCreateSheet = false
|
||||
newPattern = ""
|
||||
newDescription = ""
|
||||
},
|
||||
onCreate = {
|
||||
viewModel.createRule(newPattern, newAction, newDescription.ifBlank { null })
|
||||
showCreateSheet = false
|
||||
newPattern = ""
|
||||
newDescription = ""
|
||||
},
|
||||
pattern = newPattern,
|
||||
onPatternChange = { newPattern = it },
|
||||
action = newAction,
|
||||
onActionChange = { newAction = it },
|
||||
description = newDescription,
|
||||
onDescriptionChange = { newDescription = it },
|
||||
isLoading = uiState.isCreating
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpamShieldContent(
|
||||
uiState: SpamShieldViewModel.SpamShieldUiState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
SpamStatsRow(
|
||||
blocked = uiState.totalBlocked,
|
||||
flagged = uiState.totalFlagged,
|
||||
active = uiState.activeRules
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.rules.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Rules (${uiState.rules.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
items(uiState.rules) { rule ->
|
||||
RuleCard(rule) { enabled ->
|
||||
viewModel.toggleRule(rule.id, enabled)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
ShieldEmptyState(
|
||||
title = "No rules",
|
||||
description = "Create spam filtering rules to protect your phone",
|
||||
actionButton = {
|
||||
ShieldButton(
|
||||
text = "Create Rule",
|
||||
onClick = { /* handled by parent */ },
|
||||
variant = ShieldButtonVariant.Primary
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpamStatsRow(
|
||||
blocked: Int,
|
||||
flagged: Int,
|
||||
active: Int
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
StatCard("Blocked", blocked, modifier = Modifier.weight(1f))
|
||||
StatCard("Flagged", flagged, modifier = Modifier.weight(1f))
|
||||
StatCard("Active", active, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatCard(
|
||||
label: String,
|
||||
value: Int,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
ShieldCard(
|
||||
modifier = modifier
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "$value",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RuleCard(
|
||||
rule: com.shieldai.android.data.model.SpamRule,
|
||||
onToggle: (Boolean) -> Unit
|
||||
) {
|
||||
ShieldCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = rule.pattern,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
ShieldBadge(
|
||||
text = rule.action,
|
||||
variant = if (rule.action == "block") com.shieldai.android.ui.components.BadgeVariant.Error
|
||||
else com.shieldai.android.ui.components.BadgeVariant.Warning
|
||||
)
|
||||
if (rule.priority > 0) {
|
||||
ShieldBadge(
|
||||
text = "P${rule.priority}",
|
||||
variant = com.shieldai.android.ui.components.BadgeVariant.Info
|
||||
)
|
||||
}
|
||||
}
|
||||
rule.description?.let {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
Switch(
|
||||
checked = rule.enabled,
|
||||
onCheckedChange = onToggle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CreateRuleSheet(
|
||||
onDismiss: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
pattern: String,
|
||||
onPatternChange: (String) -> Unit,
|
||||
action: String,
|
||||
onActionChange: (String) -> Unit,
|
||||
description: String,
|
||||
onDescriptionChange: (String) -> Unit,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Create Spam Rule",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
ShieldTextField(
|
||||
value = pattern,
|
||||
onValueChange = onPatternChange,
|
||||
label = "Pattern (phone number or keyword)",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
ShieldTextField(
|
||||
value = action,
|
||||
onValueChange = onActionChange,
|
||||
label = "Action (block, flag, log)",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
ShieldTextField(
|
||||
value = description,
|
||||
onValueChange = onDescriptionChange,
|
||||
label = "Description (optional)",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
ShieldButton(
|
||||
text = "Cancel",
|
||||
onClick = onDismiss,
|
||||
variant = ShieldButtonVariant.Secondary,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ShieldButton(
|
||||
text = "Create",
|
||||
onClick = onCreate,
|
||||
variant = ShieldButtonVariant.Primary,
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = pattern.isNotBlank(),
|
||||
loading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package com.shieldai.android.ui.screens.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.shieldai.android.ui.components.ShieldAvatar
|
||||
import com.shieldai.android.ui.components.ShieldBadge
|
||||
import com.shieldai.android.ui.components.ShieldButton
|
||||
import com.shieldai.android.ui.components.ShieldButtonVariant
|
||||
import com.shieldai.android.ui.components.ShieldCard
|
||||
import com.shieldai.android.ui.components.ShieldEmptyState
|
||||
import com.shieldai.android.viewmodel.AuthViewModel
|
||||
import com.shieldai.android.viewmodel.SettingsViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
onBack: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory),
|
||||
authViewModel: AuthViewModel = viewModel(factory = AuthViewModel.Factory)
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var showLogoutDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = { Text("Settings", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onBack) { Text("Back") }
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
when {
|
||||
uiState.isLoading -> {
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
contentAlignment = androidx.compose.ui.Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
uiState.user == null -> {
|
||||
ShieldEmptyState(
|
||||
title = "Failed to load settings",
|
||||
description = uiState.error ?: "Unable to load your settings",
|
||||
actionButton = {
|
||||
TextButton(onClick = { viewModel.refresh() }) {
|
||||
Text("Retry")
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
SettingsContent(
|
||||
uiState = uiState,
|
||||
onToggleNotifications = { viewModel.toggleNotifications(it) },
|
||||
onToggleDarkMode = { viewModel.toggleDarkMode(it) },
|
||||
onToggleBiometric = { viewModel.toggleBiometric(it) },
|
||||
onUpgradeSubscription = { viewModel.upgradeSubscription() },
|
||||
onShowLogoutDialog = { showLogoutDialog = true },
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showLogoutDialog) {
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = { showLogoutDialog = false },
|
||||
title = { Text("Logout") },
|
||||
text = { Text("Are you sure you want to logout?") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
authViewModel.logout()
|
||||
showLogoutDialog = false
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = "Logout",
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showLogoutDialog = false }) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsContent(
|
||||
uiState: SettingsViewModel.SettingsUiState,
|
||||
onToggleNotifications: (Boolean) -> Unit,
|
||||
onToggleDarkMode: (Boolean) -> Unit,
|
||||
onToggleBiometric: (Boolean) -> Unit,
|
||||
onUpgradeSubscription: () -> Unit,
|
||||
onShowLogoutDialog: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val user = uiState.user!!
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
AccountSection(user)
|
||||
}
|
||||
|
||||
item {
|
||||
SubscriptionSection(
|
||||
subscription = uiState.subscription,
|
||||
onUpgrade = onUpgradeSubscription
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferencesSection(
|
||||
notificationsEnabled = uiState.notificationsEnabled,
|
||||
darkModeEnabled = uiState.darkModeEnabled,
|
||||
biometricEnabled = uiState.biometricEnabled,
|
||||
onToggleNotifications = onToggleNotifications,
|
||||
onToggleDarkMode = onToggleDarkMode,
|
||||
onToggleBiometric = onToggleBiometric
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ShieldButton(
|
||||
text = "Logout",
|
||||
onClick = onShowLogoutDialog,
|
||||
variant = ShieldButtonVariant.Danger,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountSection(user: com.shieldai.android.data.model.User) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Account",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
ShieldAvatar(
|
||||
name = user.name,
|
||||
avatarUrl = user.avatarUrl
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = user.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = user.email,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (user.emailVerified) {
|
||||
ShieldBadge(text = "Email verified", variant = com.shieldai.android.ui.components.BadgeVariant.Success)
|
||||
}
|
||||
if (user.phoneVerified) {
|
||||
ShieldBadge(text = "Phone verified", variant = com.shieldai.android.ui.components.BadgeVariant.Success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubscriptionSection(
|
||||
subscription: com.shieldai.android.data.model.Subscription?,
|
||||
onUpgrade: () -> Unit
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Subscription",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
ShieldCard {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = subscription?.plan ?: "Free",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = subscription?.status ?: "No subscription",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
ShieldButton(
|
||||
text = "Upgrade",
|
||||
onClick = onUpgrade,
|
||||
variant = ShieldButtonVariant.Secondary,
|
||||
size = com.shieldai.android.ui.components.ShieldButtonSize.Small
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferencesSection(
|
||||
notificationsEnabled: Boolean,
|
||||
darkModeEnabled: Boolean,
|
||||
biometricEnabled: Boolean,
|
||||
onToggleNotifications: (Boolean) -> Unit,
|
||||
onToggleDarkMode: (Boolean) -> Unit,
|
||||
onToggleBiometric: (Boolean) -> Unit
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Preferences",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
ShieldCard {
|
||||
Column {
|
||||
SettingRow(
|
||||
title = "Notifications",
|
||||
description = "Receive push notifications for alerts",
|
||||
checked = notificationsEnabled,
|
||||
onCheckedChange = onToggleNotifications
|
||||
)
|
||||
Divider()
|
||||
SettingRow(
|
||||
title = "Dark Mode",
|
||||
description = "Use dark theme",
|
||||
checked = darkModeEnabled,
|
||||
onCheckedChange = onToggleDarkMode
|
||||
)
|
||||
Divider()
|
||||
SettingRow(
|
||||
title = "Biometric Auth",
|
||||
description = "Use fingerprint or face unlock",
|
||||
checked = biometricEnabled,
|
||||
onCheckedChange = onToggleBiometric
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingRow(
|
||||
title: String,
|
||||
description: String,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp)
|
||||
.clickable { onCheckedChange(!checked) },
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user