2026-01-11 19:47:49 -05:00

372 lines
17 KiB
Kotlin

package com.shaarit.presentation.add
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.shaarit.ui.components.GlassCard
import com.shaarit.ui.components.GradientButton
import com.shaarit.ui.components.PremiumTextField
import com.shaarit.ui.components.SectionHeader
import com.shaarit.ui.components.TagChip
import com.shaarit.ui.theme.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddLinkScreen(
onNavigateBack: () -> Unit,
onShareSuccess: (() -> Unit)? = null,
viewModel: AddLinkViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
val url by viewModel.url.collectAsState()
val title by viewModel.title.collectAsState()
val description by viewModel.description.collectAsState()
val selectedTags by viewModel.selectedTags.collectAsState()
val newTagInput by viewModel.newTagInput.collectAsState()
val availableTags by viewModel.availableTags.collectAsState()
val isPrivate by viewModel.isPrivate.collectAsState()
val tagSuggestions by viewModel.tagSuggestions.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(uiState) {
when (val state = uiState) {
is AddLinkUiState.Success -> {
// If this was a share intent, finish the activity to return to source app
if (onShareSuccess != null) {
onShareSuccess()
} else {
onNavigateBack()
}
}
is AddLinkUiState.Error -> {
snackbarHostState.showSnackbar(state.message)
}
is AddLinkUiState.Conflict -> {
// Show conflict dialog - handled in AlertDialog below
}
else -> {}
}
}
// Conflict Dialog
if (uiState is AddLinkUiState.Conflict) {
val conflict = uiState as AddLinkUiState.Conflict
AlertDialog(
onDismissRequest = { viewModel.dismissConflict() },
title = {
Text("Link Already Exists", fontWeight = FontWeight.Bold, color = TextPrimary)
},
text = {
Column {
Text("A link with this URL already exists:", color = TextSecondary)
Spacer(modifier = Modifier.height(8.dp))
Text(
conflict.existingTitle ?: "Untitled",
color = CyanPrimary,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
Text(
"Would you like to update the existing link instead?",
color = TextSecondary
)
}
},
confirmButton = {
TextButton(onClick = { viewModel.forceUpdateExistingLink() }) {
Text("Update", color = CyanPrimary)
}
},
dismissButton = {
TextButton(onClick = { viewModel.dismissConflict() }) {
Text("Cancel", color = TextMuted)
}
},
containerColor = CardBackground,
titleContentColor = TextPrimary,
textContentColor = TextSecondary
)
}
Box(
modifier =
Modifier.fillMaxSize()
.background(
brush =
Brush.verticalGradient(
colors = listOf(DeepNavy, DarkNavy)
)
)
) {
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
TopAppBar(
title = {
Text(
"Add Link",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
},
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
Icons.Default.ArrowBack,
contentDescription = "Back",
tint = TextPrimary
)
}
},
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = DeepNavy.copy(alpha = 0.9f),
titleContentColor = TextPrimary
)
)
},
containerColor =
android.graphics.Color.TRANSPARENT.let {
androidx.compose.ui.graphics.Color.Transparent
}
) { paddingValues ->
Column(
modifier =
Modifier.padding(paddingValues)
.padding(16.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
// URL Section
GlassCard(modifier = Modifier.fillMaxWidth()) {
Column {
SectionHeader(title = "URL", subtitle = "Required")
Spacer(modifier = Modifier.height(12.dp))
PremiumTextField(
value = url,
onValueChange = { viewModel.url.value = it },
modifier = Modifier.fillMaxWidth(),
placeholder = "https://example.com",
leadingIcon = {
Icon(
Icons.Default.Share,
contentDescription = null,
tint = CyanPrimary
)
}
)
}
}
// Title Section
GlassCard(modifier = Modifier.fillMaxWidth()) {
Column {
SectionHeader(
title = "Title",
subtitle = "Optional - auto-fetched if empty"
)
Spacer(modifier = Modifier.height(12.dp))
PremiumTextField(
value = title,
onValueChange = { viewModel.title.value = it },
modifier = Modifier.fillMaxWidth(),
placeholder = "Page title"
)
}
}
// Description Section
GlassCard(modifier = Modifier.fillMaxWidth()) {
Column {
SectionHeader(title = "Description", subtitle = "Optional")
Spacer(modifier = Modifier.height(12.dp))
PremiumTextField(
value = description,
onValueChange = { viewModel.description.value = it },
modifier = Modifier.fillMaxWidth(),
placeholder = "Add a description...",
singleLine = false,
minLines = 3
)
}
}
// Tags Section
GlassCard(modifier = Modifier.fillMaxWidth()) {
Column {
SectionHeader(title = "Tags", subtitle = "Organize your links")
Spacer(modifier = Modifier.height(12.dp))
// Selected tags
if (selectedTags.isNotEmpty()) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(bottom = 12.dp)
) {
items(selectedTags) { tag ->
TagChip(
tag = tag,
isSelected = true,
onClick = { viewModel.removeTag(tag) }
)
}
}
}
// New tag input
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
PremiumTextField(
value = newTagInput,
onValueChange = { viewModel.onNewTagInputChanged(it) },
modifier = Modifier.weight(1f),
placeholder = "Add tag..."
)
IconButton(
onClick = { viewModel.addNewTag() },
enabled = newTagInput.isNotBlank()
) {
Icon(
Icons.Default.Add,
contentDescription = "Add tag",
tint =
if (newTagInput.isNotBlank()) CyanPrimary
else TextMuted
)
}
}
// Tag suggestions
AnimatedVisibility(
visible = tagSuggestions.isNotEmpty(),
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
Column(modifier = Modifier.padding(top = 12.dp)) {
Text(
"Suggestions",
style = MaterialTheme.typography.labelMedium,
color = TextMuted,
modifier = Modifier.padding(bottom = 8.dp)
)
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
items(tagSuggestions.take(10)) { tag ->
TagChip(
tag = tag.name,
isSelected = false,
onClick = { viewModel.addTag(tag.name) },
count = tag.occurrences
)
}
}
}
}
// Popular tags from existing
if (tagSuggestions.isEmpty() && availableTags.isNotEmpty()) {
Column(modifier = Modifier.padding(top = 12.dp)) {
Text(
"Popular tags",
style = MaterialTheme.typography.labelMedium,
color = TextMuted,
modifier = Modifier.padding(bottom = 8.dp)
)
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
items(
availableTags
.filter { it.name !in selectedTags }
.take(10)
) { tag ->
TagChip(
tag = tag.name,
isSelected = false,
onClick = { viewModel.addTag(tag.name) },
count = tag.occurrences
)
}
}
}
}
}
}
// Privacy Section
GlassCard(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
"Private",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
color = TextPrimary
)
Text(
"Only you can see this link",
style = MaterialTheme.typography.bodySmall,
color = TextSecondary
)
}
Switch(
checked = isPrivate,
onCheckedChange = { viewModel.isPrivate.value = it },
colors =
SwitchDefaults.colors(
checkedThumbColor = CyanPrimary,
checkedTrackColor = CyanPrimary.copy(alpha = 0.3f),
uncheckedThumbColor = TextMuted,
uncheckedTrackColor = SurfaceVariant
)
)
}
}
Spacer(modifier = Modifier.weight(1f))
// Save Button
GradientButton(
text = if (uiState is AddLinkUiState.Loading) "Saving..." else "Save Link",
onClick = { viewModel.addLink() },
modifier = Modifier.fillMaxWidth(),
enabled = url.isNotBlank() && uiState !is AddLinkUiState.Loading
)
if (uiState is AddLinkUiState.Loading) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = CyanPrimary,
trackColor = SurfaceVariant
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}