diff --git a/composeApp/src/androidMain/kotlin/friends/mobile/wishplaces/WishPlacesComponents.kt b/composeApp/src/androidMain/kotlin/friends/mobile/wishplaces/WishPlacesComponents.kt index 32e1830..567c489 100644 --- a/composeApp/src/androidMain/kotlin/friends/mobile/wishplaces/WishPlacesComponents.kt +++ b/composeApp/src/androidMain/kotlin/friends/mobile/wishplaces/WishPlacesComponents.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -20,8 +21,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.Button import androidx.compose.material3.Card @@ -48,6 +51,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import friends.mobile.designkit.DesignTheme import friends.mobile.feature.wishplaces.domain.model.WishPlace @OptIn(ExperimentalMaterial3Api::class) @@ -244,37 +248,149 @@ fun CreateWishPlaceBottomSheet( @OptIn(ExperimentalMaterial3Api::class) @Composable fun WishPlaceDetailBottomSheet(place: WishPlace, onDismiss: () -> Unit) { - ModalBottomSheet(onDismissRequest = onDismiss) { + ModalBottomSheet( + onDismissRequest = onDismiss, + scrimColor = Color.Black.copy(alpha = 0.3f), + containerColor = Color.White, + shape = RoundedCornerShape( + topStart = 12.dp, + topEnd = 12.dp + ) + ) { Column( modifier = Modifier - .padding(16.dp) .fillMaxWidth() - .padding(bottom = 32.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) + .padding(DesignTheme.Spacing.lg) + .height(175.dp), + verticalArrangement = Arrangement.spacedBy(DesignTheme.Spacing.md) ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top + ) { + Text( + text = place.title, + style = DesignTheme.Typography.heading, + maxLines = 1, + modifier = Modifier.weight(1f) + ) + + Button( + onClick = onDismiss, + modifier = Modifier.size(24.dp), + shape = RoundedCornerShape(50), + colors = androidx.compose.material3.ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + contentPadding = PaddingValues(0.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + tint = Color.Gray, + modifier = Modifier.size(20.dp) + ) + } + } + + place.location?.let { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(DesignTheme.Spacing.xs), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.LocationOn, + contentDescription = null, + modifier = Modifier.size(14.dp), + tint = DesignTheme.Colors.primary + ) + Text( + text = it, + style = DesignTheme.Typography.body, + color = Color.Black, + maxLines = 1 + ) + } + } + + if (!place.description.isNullOrEmpty()) { + Text( + text = place.description!!, + style = DesignTheme.Typography.body, + color = Color.Black, + maxLines = 2 + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(DesignTheme.Spacing.md), + verticalAlignment = Alignment.CenterVertically + ) { + StatusBadgeCompact(status = place.status.name) + + if (!place.link.isNullOrEmpty()) { + androidx.compose.material3.TextButton( + onClick = { + val intent = android.content.Intent( + android.content.Intent.ACTION_VIEW, + android.net.Uri.parse(place.link) + ) + // Context needed - will be handled in caller + }, + modifier = Modifier.height(28.dp), + shape = RoundedCornerShape(DesignTheme.CornerRadius.capsule), + colors = androidx.compose.material3.ButtonDefaults.buttonColors( + containerColor = DesignTheme.Colors.primary + ), + contentPadding = PaddingValues( + horizontal = DesignTheme.Spacing.md, + vertical = DesignTheme.Spacing.xs + ) + ) { + Icon( + imageVector = Icons.Default.OpenInNew, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(12.dp) + ) + Spacer(modifier = Modifier.width(DesignTheme.Spacing.xs)) + Text( + "Link", + style = DesignTheme.Typography.bodySmall, + color = Color.White + ) + } + } + + Spacer(modifier = Modifier.weight(1f)) + } + Text( - text = place.title, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold + text = "Added ${place.createdAt.take(10)}", + style = DesignTheme.Typography.bodySmallest, + color = Color.Gray ) - DetailRow("Location", place.location ?: "Not specified") - DetailRow("Description", place.description ?: "No description") - DetailRow("Link", place.link ?: "No link") - DetailRow("Status", place.status.name) - DetailRow("Added", place.createdAt) } } } @Composable -private fun DetailRow(label: String, value: String) { - Column { - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary - ) - Text(text = value, style = MaterialTheme.typography.bodyLarge) - } +private fun StatusBadgeCompact(status: String) { + Text( + text = status.lowercase().replaceFirstChar { it.uppercase() }, + style = DesignTheme.Typography.bodySmallest, + color = Color.White, + modifier = Modifier + .background( + color = DesignTheme.Colors.primary, + shape = RoundedCornerShape(DesignTheme.CornerRadius.capsule) + ) + .padding( + horizontal = DesignTheme.Spacing.md, + vertical = DesignTheme.Spacing.xs + ) + ) } diff --git a/iosApp/iosApp/Modules/Profile/WishPlaces/WishPlaceDetailSheet.swift b/iosApp/iosApp/Modules/Profile/WishPlaces/WishPlaceDetailSheet.swift index 2f53739..66e7490 100644 --- a/iosApp/iosApp/Modules/Profile/WishPlaces/WishPlaceDetailSheet.swift +++ b/iosApp/iosApp/Modules/Profile/WishPlaces/WishPlaceDetailSheet.swift @@ -14,58 +14,85 @@ struct WishPlaceDetailSheet: View { @Environment(\.dismiss) var dismiss var body: some View { - NavigationStack { - ScrollView { - VStack(alignment: .leading, spacing: 20) { - Text(place.title) - .font(.title2) - .fontWeight(.bold) - Divider() - DetailRow(label: "Location", value: place.location ?? "Not specified") - DetailRow(label: "Description", value: place.description ?? "No description") - if let link = place.link, !link.isEmpty { - VStack(alignment: .leading, spacing: 4) { - Text("Link") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.blue) - Link(link, destination: URL(string: link) ?? URL(fileURLWithPath: "")) - .font(.body) - .lineLimit(1) - } - } - DetailRow(label: "Status", value: place.status.name) - DetailRow(label: "Added", value: place.createdAt) - Spacer() + VStack(alignment: .leading, spacing: DesignTheme.Spacing.md) { + HStack { + Text(place.title) + .font(DesignTheme.Typography.heading) + .lineLimit(1) + .padding(.top, DesignTheme.Spacing.lg) + + Spacer() + + Button(action: { dismiss() }) { + Image(systemName: "xmark.circle.fill") + .font(.system(size: 20)) + .foregroundColor(.gray) + } + } + + if let location = place.location { + HStack(spacing: DesignTheme.Spacing.xs) { + Image(systemName: "mappin.circle.fill") + .foregroundColor(DesignTheme.accentColor) + .font(.system(size: 14)) + Text(location) + .font(DesignTheme.Typography.body) + .foregroundColor(.primary) + .lineLimit(1) } - .padding() } - .navigationTitle("Wish Place Details") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - dismiss() + + if let description = place.description_ { + Text(description) + .font(DesignTheme.Typography.body) + .foregroundColor(.primary) + .lineLimit(2) + } + + HStack(spacing: DesignTheme.Spacing.md) { + StatusBadge(status: place.status.name) + + if let link = place.link, !link.isEmpty { + Link(destination: URL(string: link) ?? URL(fileURLWithPath: "")) { + HStack(spacing: DesignTheme.Spacing.xs) { + Image(systemName: "arrow.up.right") + .font(.system(size: 12, weight: .semibold)) + Text("Link") + .font(DesignTheme.Typography.bodySmall) + } + .foregroundColor(.white) + .padding(.horizontal, DesignTheme.Spacing.md) + .padding(.vertical, DesignTheme.Spacing.xs) + .background(DesignTheme.accentColor) + .cornerRadius(DesignTheme.CornerRadius.capsule) } } + + Spacer() } + + Text("Added \(place.createdAt.prefix(10))") + .font(DesignTheme.Typography.bodySmallest) + .foregroundColor(.gray) } + .padding(DesignTheme.Spacing.lg) + .background(Color(.systemBackground)) + .cornerRadius(DesignTheme.CornerRadius.capsule) + .presentationDetents([.height(175)]) + .presentationBackground(.white) } } -private struct DetailRow: View { - let label: String - let value: String +private struct StatusBadge: View { + let status: String var body: some View { - VStack(alignment: .leading, spacing: 4) { - Text(label) - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.blue) - Text(value) - .font(.body) - .foregroundColor(.primary) - } + Text(status.lowercased().prefix(1).uppercased() + status.lowercased().dropFirst()) + .font(DesignTheme.Typography.bodySmallest) + .foregroundColor(.white) + .padding(.horizontal, DesignTheme.Spacing.md) + .padding(.vertical, DesignTheme.Spacing.xs) + .background(DesignTheme.accentColor) + .cornerRadius(DesignTheme.CornerRadius.capsule) } }