Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
)
)
}
111 changes: 69 additions & 42 deletions iosApp/iosApp/Modules/Profile/WishPlaces/WishPlaceDetailSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading