372 lines
17 KiB
Kotlin
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))
|
|
}
|
|
}
|
|
}
|
|
}
|