From 77a118a6f7920c4c91a07c28c0551a04842aabd2 Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 10 Nov 2025 11:38:17 -0600 Subject: [PATCH] Refactor iOS and Android views into separate files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Organized view components by extracting composables and views into separate files following single responsibility principle. iOS Changes: - Split MainTabView → extracted ProfileTabView - Split CompleteTaskView → extracted ImageThumbnailView, CameraPickerView - Split ManageUsersView → extracted ShareCodeCard, UserListItem - Consolidated task action buttons into single TaskActionButtons.swift file - Split HomeScreenView → extracted OverviewCard, StatView, HomeNavigationCard - Split AllTasksView → extracted DynamicTaskColumnView, DynamicTaskCard - Split ContentView → extracted ComposeView, CustomView Android Changes: - Split ResetPasswordScreen → extracted RequirementItem component - Split TasksScreen → extracted TaskPill component - Created TaskDisplayUtils for shared helper functions (getIconFromName, hexToColor) All extracted components properly organized in: - iOS: Subviews/Common, Subviews/Task, Subviews/Residence, Profile - Android: ui/components/auth, ui/components/task, ui/utils 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ui/components/auth/RequirementItem.kt | 36 +++ .../mycrib/ui/components/task/TaskPill.kt | 42 ++++ .../mycrib/ui/screens/ResetPasswordScreen.kt | 21 +- .../example/mycrib/ui/screens/TasksScreen.kt | 108 +------- .../mycrib/ui/utils/TaskDisplayUtils.kt | 60 +++++ iosApp/iosApp/ContentView.swift | 37 --- iosApp/iosApp/HomeScreenView.swift | 98 -------- iosApp/iosApp/MainTabView.swift | 70 ------ iosApp/iosApp/Profile/ProfileTabView.swift | 71 ++++++ iosApp/iosApp/Residence/ManageUsersView.swift | 112 --------- .../Subviews/Common/CameraPickerView.swift | 38 +++ .../iosApp/Subviews/Common/ComposeView.swift | 11 + .../iosApp/Subviews/Common/CustomView.swift | 28 +++ .../Subviews/Common/HomeNavigationCard.swift | 36 +++ .../Subviews/Common/ImageThumbnailView.swift | 32 +++ .../iosApp/Subviews/Common/OverviewCard.swift | 43 ++++ iosApp/iosApp/Subviews/Common/StatView.swift | 23 ++ .../Subviews/Residence/ShareCodeCard.swift | 53 ++++ .../Subviews/Residence/UserListItem.swift | 63 +++++ .../Subviews/Task/DynamicTaskCard.swift | 159 ++++++++++++ .../Subviews/Task/DynamicTaskColumnView.swift | 81 ++++++ iosApp/iosApp/Task/AllTasksView.swift | 236 ------------------ iosApp/iosApp/Task/CompleteTaskView.swift | 69 ----- 23 files changed, 780 insertions(+), 747 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/auth/RequirementItem.kt create mode 100644 composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/task/TaskPill.kt create mode 100644 composeApp/src/commonMain/kotlin/com/example/mycrib/ui/utils/TaskDisplayUtils.kt create mode 100644 iosApp/iosApp/Profile/ProfileTabView.swift create mode 100644 iosApp/iosApp/Subviews/Common/CameraPickerView.swift create mode 100644 iosApp/iosApp/Subviews/Common/ComposeView.swift create mode 100644 iosApp/iosApp/Subviews/Common/CustomView.swift create mode 100644 iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift create mode 100644 iosApp/iosApp/Subviews/Common/ImageThumbnailView.swift create mode 100644 iosApp/iosApp/Subviews/Common/OverviewCard.swift create mode 100644 iosApp/iosApp/Subviews/Common/StatView.swift create mode 100644 iosApp/iosApp/Subviews/Residence/ShareCodeCard.swift create mode 100644 iosApp/iosApp/Subviews/Residence/UserListItem.swift create mode 100644 iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift create mode 100644 iosApp/iosApp/Subviews/Task/DynamicTaskColumnView.swift diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/auth/RequirementItem.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/auth/RequirementItem.kt new file mode 100644 index 0000000..ca0769f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/auth/RequirementItem.kt @@ -0,0 +1,36 @@ +package com.mycrib.android.ui.components.auth + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Circle +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun RequirementItem(text: String, satisfied: Boolean) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + if (satisfied) Icons.Default.CheckCircle else Icons.Default.Circle, + contentDescription = null, + tint = if (satisfied) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text, + style = MaterialTheme.typography.bodySmall, + color = if (satisfied) MaterialTheme.colorScheme.onSecondaryContainer else MaterialTheme.colorScheme.onSurfaceVariant + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/task/TaskPill.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/task/TaskPill.kt new file mode 100644 index 0000000..eaaea7a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/components/task/TaskPill.kt @@ -0,0 +1,42 @@ +package com.mycrib.android.ui.components.task + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun TaskPill( + count: Int, + label: String, + color: Color +) { + Surface( + color = color.copy(alpha = 0.1f), + shape = MaterialTheme.shapes.small + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = count.toString(), + style = MaterialTheme.typography.labelLarge, + color = color + ) + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = color + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResetPasswordScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResetPasswordScreen.kt index 13cbd3d..38cb3ef 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResetPasswordScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResetPasswordScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.mycrib.android.ui.components.auth.AuthHeader +import com.mycrib.android.ui.components.auth.RequirementItem import com.mycrib.android.ui.components.common.ErrorCard import com.mycrib.android.viewmodel.PasswordResetViewModel import com.mycrib.shared.network.ApiResult @@ -254,23 +255,3 @@ fun ResetPasswordScreen( } } } - -@Composable -private fun RequirementItem(text: String, satisfied: Boolean) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - if (satisfied) Icons.Default.CheckCircle else Icons.Default.Circle, - contentDescription = null, - tint = if (satisfied) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text, - style = MaterialTheme.typography.bodySmall, - color = if (satisfied) MaterialTheme.colorScheme.onSecondaryContainer else MaterialTheme.colorScheme.onSurfaceVariant - ) - } -} diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/TasksScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/TasksScreen.kt index 31538ce..18f531b 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/TasksScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/TasksScreen.kt @@ -12,6 +12,9 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.mycrib.android.ui.components.CompleteTaskDialog import com.mycrib.android.ui.components.task.TaskCard +import com.mycrib.android.ui.components.task.TaskPill +import com.mycrib.android.ui.utils.getIconFromName +import com.mycrib.android.ui.utils.hexToColor import com.mycrib.android.viewmodel.TaskCompletionViewModel import com.mycrib.android.viewmodel.TaskViewModel import com.mycrib.shared.network.ApiResult @@ -271,108 +274,3 @@ fun TasksScreen( ) } } - -@Composable -private fun TaskPill( - count: Int, - label: String, - color: androidx.compose.ui.graphics.Color -) { - Surface( - color = color.copy(alpha = 0.1f), - shape = MaterialTheme.shapes.small - ) { - Row( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically - ) { - Text( - text = count.toString(), - style = MaterialTheme.typography.labelLarge, - color = color - ) - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = color - ) - } - } -} - -@Composable -private fun getColumnColor(columnName: String): androidx.compose.ui.graphics.Color { - return when (columnName) { - "upcoming_tasks" -> MaterialTheme.colorScheme.primary - "in_progress_tasks" -> MaterialTheme.colorScheme.tertiary - "done_tasks" -> MaterialTheme.colorScheme.secondary - "archived_tasks" -> MaterialTheme.colorScheme.outline - else -> MaterialTheme.colorScheme.primary // Default color for unknown columns - } -} - -@Composable -private fun getColumnIcon(columnName: String): androidx.compose.ui.graphics.vector.ImageVector { - return when (columnName) { - "upcoming_tasks" -> Icons.Default.CalendarToday - "in_progress_tasks" -> Icons.Default.PlayArrow - "done_tasks" -> Icons.Default.CheckCircle - "archived_tasks" -> Icons.Default.Archive - else -> Icons.Default.List // Default icon for unknown columns - } -} - -/** - * Helper function to convert icon name string to ImageVector - */ -private fun getIconFromName(iconName: String): androidx.compose.ui.graphics.vector.ImageVector { - return when (iconName) { - "CalendarToday" -> Icons.Default.CalendarToday - "PlayCircle" -> Icons.Default.PlayCircle - "PlayArrow" -> Icons.Default.PlayArrow - "CheckCircle" -> Icons.Default.CheckCircle - "Archive" -> Icons.Default.Archive - "List" -> Icons.Default.List - "Unarchive" -> Icons.Default.Unarchive - else -> Icons.Default.List // Default fallback - } -} - -/** - * Helper function to convert hex color string to Color - * Supports formats: #RGB, #RRGGBB, #AARRGGBB - * Platform-independent implementation - */ -private fun hexToColor(hex: String): androidx.compose.ui.graphics.Color { - val cleanHex = hex.removePrefix("#") - return try { - when (cleanHex.length) { - 3 -> { - // RGB format - expand to RRGGBB - val r = cleanHex[0].toString().repeat(2).toInt(16) - val g = cleanHex[1].toString().repeat(2).toInt(16) - val b = cleanHex[2].toString().repeat(2).toInt(16) - androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f) - } - 6 -> { - // RRGGBB format - val r = cleanHex.substring(0, 2).toInt(16) - val g = cleanHex.substring(2, 4).toInt(16) - val b = cleanHex.substring(4, 6).toInt(16) - androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f) - } - 8 -> { - // AARRGGBB format - val a = cleanHex.substring(0, 2).toInt(16) - val r = cleanHex.substring(2, 4).toInt(16) - val g = cleanHex.substring(4, 6).toInt(16) - val b = cleanHex.substring(6, 8).toInt(16) - androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f, alpha = a / 255f) - } - else -> androidx.compose.ui.graphics.Color.Gray // Default fallback - } - } catch (e: Exception) { - androidx.compose.ui.graphics.Color.Gray // Fallback on parse error - } -} diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/utils/TaskDisplayUtils.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/utils/TaskDisplayUtils.kt new file mode 100644 index 0000000..410e7fc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/utils/TaskDisplayUtils.kt @@ -0,0 +1,60 @@ +package com.mycrib.android.ui.utils + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector + +/** + * Helper function to convert icon name string to ImageVector + */ +fun getIconFromName(iconName: String): ImageVector { + return when (iconName) { + "CalendarToday" -> Icons.Default.CalendarToday + "PlayCircle" -> Icons.Default.PlayCircle + "PlayArrow" -> Icons.Default.PlayArrow + "CheckCircle" -> Icons.Default.CheckCircle + "Archive" -> Icons.Default.Archive + "List" -> Icons.Default.List + "Unarchive" -> Icons.Default.Unarchive + else -> Icons.Default.List // Default fallback + } +} + +/** + * Helper function to convert hex color string to Color + * Supports formats: #RGB, #RRGGBB, #AARRGGBB + * Platform-independent implementation + */ +fun hexToColor(hex: String): Color { + val cleanHex = hex.removePrefix("#") + return try { + when (cleanHex.length) { + 3 -> { + // RGB format - expand to RRGGBB + val r = cleanHex[0].toString().repeat(2).toInt(16) + val g = cleanHex[1].toString().repeat(2).toInt(16) + val b = cleanHex[2].toString().repeat(2).toInt(16) + Color(red = r / 255f, green = g / 255f, blue = b / 255f) + } + 6 -> { + // RRGGBB format + val r = cleanHex.substring(0, 2).toInt(16) + val g = cleanHex.substring(2, 4).toInt(16) + val b = cleanHex.substring(4, 6).toInt(16) + Color(red = r / 255f, green = g / 255f, blue = b / 255f) + } + 8 -> { + // AARRGGBB format + val a = cleanHex.substring(0, 2).toInt(16) + val r = cleanHex.substring(2, 4).toInt(16) + val g = cleanHex.substring(4, 6).toInt(16) + val b = cleanHex.substring(6, 8).toInt(16) + Color(red = r / 255f, green = g / 255f, blue = b / 255f, alpha = a / 255f) + } + else -> Color.Gray // Default fallback + } + } catch (e: Exception) { + Color.Gray // Fallback on parse error + } +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 75c3f77..6acefd9 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -1,46 +1,9 @@ -import UIKit import SwiftUI import ComposeApp -struct ComposeView: UIViewControllerRepresentable { - func makeUIViewController(context: Context) -> UIViewController { - MainViewControllerKt.MainViewController() - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} - - -} - struct ContentView: View { var body: some View { CustomView() .ignoresSafeArea() } } - -struct CustomView: View { - var body: some View { - Text("Custom view") - .task { - await ViewModel().somethingRandom() - } - } -} - -class ViewModel { - func somethingRandom() async { - TokenStorage().initialize(manager: TokenManager.init()) -// TokenStorage.initialize(TokenManager.getInstance()) - - let api = ResidenceApi(client: ApiClient_iosKt.createHttpClient()) - - api.deleteResidence(token: "token", id: 32) { result, error in - if let error = error { - print("Interop error: \(error)") - return - } - guard let result = result else { return } - } - } -} diff --git a/iosApp/iosApp/HomeScreenView.swift b/iosApp/iosApp/HomeScreenView.swift index 169aebd..cace404 100644 --- a/iosApp/iosApp/HomeScreenView.swift +++ b/iosApp/iosApp/HomeScreenView.swift @@ -62,104 +62,6 @@ struct HomeScreenView: View { } } -struct OverviewCard: View { - let summary: OverallSummary - - var body: some View { - VStack(spacing: 16) { - HStack { - Image(systemName: "chart.bar.fill") - .font(.title3) - Text("Overview") - .font(.title2) - .fontWeight(.bold) - Spacer() - } - - HStack(spacing: 40) { - StatView( - icon: "house.fill", - value: "\(summary.totalResidences)", - label: "Properties" - ) - - StatView( - icon: "list.bullet", - value: "\(summary.totalTasks)", - label: "Total Tasks" - ) - - StatView( - icon: "clock.fill", - value: "\(summary.totalPending)", - label: "Pending" - ) - } - } - .padding(20) - .background(Color.blue.opacity(0.1)) - .cornerRadius(16) - .padding(.horizontal) - } -} - -struct StatView: View { - let icon: String - let value: String - let label: String - - var body: some View { - VStack(spacing: 8) { - Image(systemName: icon) - .font(.title2) - .foregroundColor(.blue) - - Text(value) - .font(.title) - .fontWeight(.bold) - - Text(label) - .font(.caption) - .foregroundColor(.secondary) - } - } -} - -struct HomeNavigationCard: View { - let icon: String - let title: String - let subtitle: String - - var body: some View { - HStack(spacing: 16) { - Image(systemName: icon) - .font(.system(size: 36)) - .foregroundColor(.blue) - .frame(width: 60) - - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.title3) - .fontWeight(.semibold) - .foregroundColor(.primary) - - Text(subtitle) - .font(.subheadline) - .foregroundColor(.secondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .foregroundColor(.secondary) - } - .padding(20) - .background(Color(.systemBackground)) - .cornerRadius(12) - .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2) - } -} - #Preview { HomeScreenView() } diff --git a/iosApp/iosApp/MainTabView.swift b/iosApp/iosApp/MainTabView.swift index af28335..086b971 100644 --- a/iosApp/iosApp/MainTabView.swift +++ b/iosApp/iosApp/MainTabView.swift @@ -33,76 +33,6 @@ struct MainTabView: View { } } -struct ProfileTabView: View { - @EnvironmentObject var loginViewModel: LoginViewModel - @State private var showingProfileEdit = false - - var body: some View { - List { - Section { - HStack { - Image(systemName: "person.circle.fill") - .resizable() - .frame(width: 60, height: 60) - .foregroundColor(.blue) - - VStack(alignment: .leading, spacing: 4) { - Text("User Profile") - .font(.headline) - - Text("Manage your account") - .font(.caption) - .foregroundColor(.secondary) - } - } - .padding(.vertical, 8) - } - - Section("Account") { - Button(action: { - showingProfileEdit = true - }) { - Label("Edit Profile", systemImage: "person.crop.circle") - .foregroundColor(.primary) - } - - NavigationLink(destination: Text("Notifications")) { - Label("Notifications", systemImage: "bell") - } - - NavigationLink(destination: Text("Privacy")) { - Label("Privacy", systemImage: "lock.shield") - } - } - - Section { - Button(action: { - loginViewModel.logout() - }) { - Label("Log Out", systemImage: "rectangle.portrait.and.arrow.right") - .foregroundColor(.red) - } - } - - Section { - VStack(alignment: .leading, spacing: 4) { - Text("MyCrib") - .font(.caption) - .fontWeight(.semibold) - - Text("Version 1.0.0") - .font(.caption2) - .foregroundColor(.secondary) - } - } - } - .navigationTitle("Profile") - .sheet(isPresented: $showingProfileEdit) { - ProfileView() - } - } -} - #Preview { MainTabView() } diff --git a/iosApp/iosApp/Profile/ProfileTabView.swift b/iosApp/iosApp/Profile/ProfileTabView.swift new file mode 100644 index 0000000..844f33f --- /dev/null +++ b/iosApp/iosApp/Profile/ProfileTabView.swift @@ -0,0 +1,71 @@ +import SwiftUI + +struct ProfileTabView: View { + @EnvironmentObject var loginViewModel: LoginViewModel + @State private var showingProfileEdit = false + + var body: some View { + List { + Section { + HStack { + Image(systemName: "person.circle.fill") + .resizable() + .frame(width: 60, height: 60) + .foregroundColor(.blue) + + VStack(alignment: .leading, spacing: 4) { + Text("User Profile") + .font(.headline) + + Text("Manage your account") + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding(.vertical, 8) + } + + Section("Account") { + Button(action: { + showingProfileEdit = true + }) { + Label("Edit Profile", systemImage: "person.crop.circle") + .foregroundColor(.primary) + } + + NavigationLink(destination: Text("Notifications")) { + Label("Notifications", systemImage: "bell") + } + + NavigationLink(destination: Text("Privacy")) { + Label("Privacy", systemImage: "lock.shield") + } + } + + Section { + Button(action: { + loginViewModel.logout() + }) { + Label("Log Out", systemImage: "rectangle.portrait.and.arrow.right") + .foregroundColor(.red) + } + } + + Section { + VStack(alignment: .leading, spacing: 4) { + Text("MyCrib") + .font(.caption) + .fontWeight(.semibold) + + Text("Version 1.0.0") + .font(.caption2) + .foregroundColor(.secondary) + } + } + } + .navigationTitle("Profile") + .sheet(isPresented: $showingProfileEdit) { + ProfileView() + } + } +} diff --git a/iosApp/iosApp/Residence/ManageUsersView.swift b/iosApp/iosApp/Residence/ManageUsersView.swift index 4effadf..48b82d3 100644 --- a/iosApp/iosApp/Residence/ManageUsersView.swift +++ b/iosApp/iosApp/Residence/ManageUsersView.swift @@ -155,118 +155,6 @@ struct ManageUsersView: View { } } -// MARK: - Share Code Card -struct ShareCodeCard: View { - let shareCode: ResidenceShareCode? - let residenceName: String - let isGeneratingCode: Bool - let onGenerateCode: () -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - VStack(alignment: .leading, spacing: 4) { - Text("Share Code") - .font(.subheadline) - .foregroundColor(.secondary) - - if let shareCode = shareCode { - Text(shareCode.code) - .font(.title) - .fontWeight(.bold) - .foregroundColor(.blue) - } else { - Text("No active code") - .font(.body) - .foregroundColor(.secondary) - } - } - - Spacer() - - Button(action: onGenerateCode) { - HStack { - Image(systemName: "square.and.arrow.up") - Text(shareCode != nil ? "New Code" : "Generate") - } - } - .buttonStyle(.borderedProminent) - .disabled(isGeneratingCode) - } - - if shareCode != nil { - Text("Share this code with others to give them access to \(residenceName)") - .font(.caption) - .foregroundColor(.secondary) - } - } - .padding() - .background(Color.blue.opacity(0.1)) - .cornerRadius(12) - } -} - -// MARK: - User List Item -struct UserListItem: View { - let user: ResidenceUser - let isOwner: Bool - let isPrimaryOwner: Bool - let onRemove: () -> Void - - var body: some View { - HStack(alignment: .top, spacing: 12) { - VStack(alignment: .leading, spacing: 4) { - HStack { - Text(user.username) - .font(.body) - .fontWeight(.medium) - - if isOwner { - Text("Owner") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.blue) - .padding(.horizontal, 6) - .padding(.vertical, 2) - .background(Color.blue.opacity(0.1)) - .cornerRadius(4) - } - } - - if !user.email.isEmpty { - Text(user.email) - .font(.caption) - .foregroundColor(.secondary) - } - - let fullName = [user.firstName, user.lastName] - .compactMap { $0 } - .filter { !$0.isEmpty } - .joined(separator: " ") - - if !fullName.isEmpty { - Text(fullName) - .font(.caption) - .foregroundColor(.secondary) - } - } - - Spacer() - - if isPrimaryOwner && !isOwner { - Button(action: onRemove) { - Image(systemName: "trash") - .foregroundColor(.red) - } - } - } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(12) - .padding(.horizontal) - } -} - #Preview { ManageUsersView(residenceId: 1, residenceName: "My Home", isPrimaryOwner: true) } diff --git a/iosApp/iosApp/Subviews/Common/CameraPickerView.swift b/iosApp/iosApp/Subviews/Common/CameraPickerView.swift new file mode 100644 index 0000000..ffb8ecc --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/CameraPickerView.swift @@ -0,0 +1,38 @@ +import SwiftUI + +struct CameraPickerView: UIViewControllerRepresentable { + @Environment(\.dismiss) var dismiss + let onImageCaptured: (UIImage) -> Void + + func makeUIViewController(context: Context) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.sourceType = .camera + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + let parent: CameraPickerView + + init(_ parent: CameraPickerView) { + self.parent = parent + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let image = info[.originalImage] as? UIImage { + parent.onImageCaptured(image) + } + parent.dismiss() + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + parent.dismiss() + } + } +} diff --git a/iosApp/iosApp/Subviews/Common/ComposeView.swift b/iosApp/iosApp/Subviews/Common/ComposeView.swift new file mode 100644 index 0000000..17b77c6 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/ComposeView.swift @@ -0,0 +1,11 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} diff --git a/iosApp/iosApp/Subviews/Common/CustomView.swift b/iosApp/iosApp/Subviews/Common/CustomView.swift new file mode 100644 index 0000000..7767b52 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/CustomView.swift @@ -0,0 +1,28 @@ +import SwiftUI +import ComposeApp + +struct CustomView: View { + var body: some View { + Text("Custom view") + .task { + await ViewModel().somethingRandom() + } + } +} + +class ViewModel { + func somethingRandom() async { + TokenStorage().initialize(manager: TokenManager.init()) +// TokenStorage.initialize(TokenManager.getInstance()) + + let api = ResidenceApi(client: ApiClient_iosKt.createHttpClient()) + + api.deleteResidence(token: "token", id: 32) { result, error in + if let error = error { + print("Interop error: \(error)") + return + } + guard let result = result else { return } + } + } +} diff --git a/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift b/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift new file mode 100644 index 0000000..5997946 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift @@ -0,0 +1,36 @@ +import SwiftUI + +struct HomeNavigationCard: View { + let icon: String + let title: String + let subtitle: String + + var body: some View { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.system(size: 36)) + .foregroundColor(.blue) + .frame(width: 60) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.title3) + .fontWeight(.semibold) + .foregroundColor(.primary) + + Text(subtitle) + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .foregroundColor(.secondary) + } + .padding(20) + .background(Color(.systemBackground)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2) + } +} diff --git a/iosApp/iosApp/Subviews/Common/ImageThumbnailView.swift b/iosApp/iosApp/Subviews/Common/ImageThumbnailView.swift new file mode 100644 index 0000000..e1ca048 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/ImageThumbnailView.swift @@ -0,0 +1,32 @@ +import SwiftUI + +struct ImageThumbnailView: View { + let image: UIImage + let onRemove: () -> Void + + var body: some View { + ZStack(alignment: .topTrailing) { + Image(uiImage: image) + .resizable() + .scaledToFill() + .frame(width: 100, height: 100) + .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + .overlay { + RoundedRectangle(cornerRadius: 12, style: .continuous) + .strokeBorder(.quaternary, lineWidth: 1) + } + + Button(action: onRemove) { + Image(systemName: "xmark.circle.fill") + .font(.title3) + .foregroundStyle(.white) + .background { + Circle() + .fill(.black.opacity(0.6)) + .padding(4) + } + } + .offset(x: 8, y: -8) + } + } +} diff --git a/iosApp/iosApp/Subviews/Common/OverviewCard.swift b/iosApp/iosApp/Subviews/Common/OverviewCard.swift new file mode 100644 index 0000000..3ac53e8 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/OverviewCard.swift @@ -0,0 +1,43 @@ +import SwiftUI +import ComposeApp + +struct OverviewCard: View { + let summary: OverallSummary + + var body: some View { + VStack(spacing: 16) { + HStack { + Image(systemName: "chart.bar.fill") + .font(.title3) + Text("Overview") + .font(.title2) + .fontWeight(.bold) + Spacer() + } + + HStack(spacing: 40) { + StatView( + icon: "house.fill", + value: "\(summary.totalResidences)", + label: "Properties" + ) + + StatView( + icon: "list.bullet", + value: "\(summary.totalTasks)", + label: "Total Tasks" + ) + + StatView( + icon: "clock.fill", + value: "\(summary.totalPending)", + label: "Pending" + ) + } + } + .padding(20) + .background(Color.blue.opacity(0.1)) + .cornerRadius(16) + .padding(.horizontal) + } +} diff --git a/iosApp/iosApp/Subviews/Common/StatView.swift b/iosApp/iosApp/Subviews/Common/StatView.swift new file mode 100644 index 0000000..ca94712 --- /dev/null +++ b/iosApp/iosApp/Subviews/Common/StatView.swift @@ -0,0 +1,23 @@ +import SwiftUI + +struct StatView: View { + let icon: String + let value: String + let label: String + + var body: some View { + VStack(spacing: 8) { + Image(systemName: icon) + .font(.title2) + .foregroundColor(.blue) + + Text(value) + .font(.title) + .fontWeight(.bold) + + Text(label) + .font(.caption) + .foregroundColor(.secondary) + } + } +} diff --git a/iosApp/iosApp/Subviews/Residence/ShareCodeCard.swift b/iosApp/iosApp/Subviews/Residence/ShareCodeCard.swift new file mode 100644 index 0000000..29445fc --- /dev/null +++ b/iosApp/iosApp/Subviews/Residence/ShareCodeCard.swift @@ -0,0 +1,53 @@ +import SwiftUI +import ComposeApp + +// MARK: - Share Code Card +struct ShareCodeCard: View { + let shareCode: ResidenceShareCode? + let residenceName: String + let isGeneratingCode: Bool + let onGenerateCode: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("Share Code") + .font(.subheadline) + .foregroundColor(.secondary) + + if let shareCode = shareCode { + Text(shareCode.code) + .font(.title) + .fontWeight(.bold) + .foregroundColor(.blue) + } else { + Text("No active code") + .font(.body) + .foregroundColor(.secondary) + } + } + + Spacer() + + Button(action: onGenerateCode) { + HStack { + Image(systemName: "square.and.arrow.up") + Text(shareCode != nil ? "New Code" : "Generate") + } + } + .buttonStyle(.borderedProminent) + .disabled(isGeneratingCode) + } + + if shareCode != nil { + Text("Share this code with others to give them access to \(residenceName)") + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding() + .background(Color.blue.opacity(0.1)) + .cornerRadius(12) + } +} diff --git a/iosApp/iosApp/Subviews/Residence/UserListItem.swift b/iosApp/iosApp/Subviews/Residence/UserListItem.swift new file mode 100644 index 0000000..05dd0e7 --- /dev/null +++ b/iosApp/iosApp/Subviews/Residence/UserListItem.swift @@ -0,0 +1,63 @@ +import SwiftUI +import ComposeApp + +// MARK: - User List Item +struct UserListItem: View { + let user: ResidenceUser + let isOwner: Bool + let isPrimaryOwner: Bool + let onRemove: () -> Void + + var body: some View { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(user.username) + .font(.body) + .fontWeight(.medium) + + if isOwner { + Text("Owner") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.blue) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.blue.opacity(0.1)) + .cornerRadius(4) + } + } + + if !user.email.isEmpty { + Text(user.email) + .font(.caption) + .foregroundColor(.secondary) + } + + let fullName = [user.firstName, user.lastName] + .compactMap { $0 } + .filter { !$0.isEmpty } + .joined(separator: " ") + + if !fullName.isEmpty { + Text(fullName) + .font(.caption) + .foregroundColor(.secondary) + } + } + + Spacer() + + if isPrimaryOwner && !isOwner { + Button(action: onRemove) { + Image(systemName: "trash") + .foregroundColor(.red) + } + } + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + .padding(.horizontal) + } +} diff --git a/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift b/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift new file mode 100644 index 0000000..b5e4e90 --- /dev/null +++ b/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift @@ -0,0 +1,159 @@ +import SwiftUI +import ComposeApp + +/// Task card that dynamically renders buttons based on the column's button types +struct DynamicTaskCard: View { + let task: TaskDetail + let buttonTypes: [String] + let onEdit: () -> Void + let onCancel: () -> Void + let onUncancel: () -> Void + let onMarkInProgress: () -> Void + let onComplete: () -> Void + let onArchive: () -> Void + let onUnarchive: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(task.title) + .font(.headline) + .foregroundColor(.primary) + + if let status = task.status { + StatusBadge(status: status.name) + } + } + + Spacer() + + PriorityBadge(priority: task.priority.name) + } + + if let description = task.description_, !description.isEmpty { + Text(description) + .font(.subheadline) + .foregroundColor(.secondary) + .lineLimit(2) + } + + HStack { + Label(task.frequency.displayName, systemImage: "repeat") + .font(.caption) + .foregroundColor(.secondary) + + Spacer() + + if let due_date = task.dueDate { + Label(formatDate(due_date), systemImage: "calendar") + .font(.caption) + .foregroundColor(.secondary) + } + } + + if task.completions.count > 0 { + Divider() + + VStack(alignment: .leading, spacing: 8) { + HStack { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + Text("Completions (\(task.completions.count))") + .font(.caption) + .fontWeight(.semibold) + } + + ForEach(task.completions, id: \.id) { completion in + CompletionCardView(completion: completion) + } + } + } + + // Render buttons based on buttonTypes array + VStack(spacing: 8) { + ForEach(Array(buttonTypes.enumerated()), id: \.offset) { index, buttonType in + renderButton(for: buttonType) + } + } + } + .padding(16) + .background(Color(.systemBackground)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.05), radius: 3, x: 0, y: 2) + } + + private func formatDate(_ dateString: String) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + if let date = formatter.date(from: dateString) { + formatter.dateStyle = .medium + return formatter.string(from: date) + } + return dateString + } + + @ViewBuilder + private func renderButton(for buttonType: String) -> some View { + switch buttonType { + case "mark_in_progress": + MarkInProgressButton( + taskId: task.id, + onCompletion: onMarkInProgress, + onError: { error in + print("Error marking in progress: \(error)") + } + ) + case "complete": + CompleteTaskButton( + taskId: task.id, + onCompletion: onComplete, + onError: { error in + print("Error completing task: \(error)") + } + ) + case "edit": + EditTaskButton( + taskId: task.id, + onCompletion: onEdit, + onError: { error in + print("Error editing task: \(error)") + } + ) + case "cancel": + CancelTaskButton( + taskId: task.id, + onCompletion: onCancel, + onError: { error in + print("Error cancelling task: \(error)") + } + ) + case "uncancel": + UncancelTaskButton( + taskId: task.id, + onCompletion: onUncancel, + onError: { error in + print("Error restoring task: \(error)") + } + ) + case "archive": + ArchiveTaskButton( + taskId: task.id, + onCompletion: onArchive, + onError: { error in + print("Error archiving task: \(error)") + } + ) + case "unarchive": + UnarchiveTaskButton( + taskId: task.id, + onCompletion: onUnarchive, + onError: { error in + print("Error unarchiving task: \(error)") + } + ) + default: + EmptyView() + } + } +} diff --git a/iosApp/iosApp/Subviews/Task/DynamicTaskColumnView.swift b/iosApp/iosApp/Subviews/Task/DynamicTaskColumnView.swift new file mode 100644 index 0000000..bcced8a --- /dev/null +++ b/iosApp/iosApp/Subviews/Task/DynamicTaskColumnView.swift @@ -0,0 +1,81 @@ +import SwiftUI +import ComposeApp + +/// Dynamic task column view that adapts based on the column configuration +struct DynamicTaskColumnView: View { + let column: TaskColumn + let onEditTask: (TaskDetail) -> Void + let onCancelTask: (Int32) -> Void + let onUncancelTask: (Int32) -> Void + let onMarkInProgress: (Int32) -> Void + let onCompleteTask: (TaskDetail) -> Void + let onArchiveTask: (Int32) -> Void + let onUnarchiveTask: (Int32) -> Void + + // Get icon from API response, with fallback + private var columnIcon: String { + column.icons["ios"] ?? "list.bullet" + } + + private var columnColor: Color { + Color(hex: column.color) ?? .primary + } + + var body: some View { + VStack(spacing: 0) { + ScrollView { + VStack(spacing: 16) { + // Header + HStack(spacing: 8) { + Image(systemName: columnIcon) + .font(.headline) + .foregroundColor(columnColor) + + Text(column.displayName) + .font(.headline) + .foregroundColor(columnColor) + + Spacer() + + Text("\(column.count)") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.white) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(columnColor) + .cornerRadius(12) + } + + if column.tasks.isEmpty { + VStack(spacing: 8) { + Image(systemName: columnIcon) + .font(.system(size: 40)) + .foregroundColor(columnColor.opacity(0.3)) + + Text("No tasks") + .font(.caption) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity) + .padding(.top, 40) + } else { + ForEach(column.tasks, id: \.id) { task in + DynamicTaskCard( + task: task, + buttonTypes: column.buttonTypes, + onEdit: { onEditTask(task) }, + onCancel: { onCancelTask(task.id) }, + onUncancel: { onUncancelTask(task.id) }, + onMarkInProgress: { onMarkInProgress(task.id) }, + onComplete: { onCompleteTask(task) }, + onArchive: { onArchiveTask(task.id) }, + onUnarchive: { onUnarchiveTask(task.id) } + ) + } + } + } + } + } + } +} diff --git a/iosApp/iosApp/Task/AllTasksView.swift b/iosApp/iosApp/Task/AllTasksView.swift index 35a5155..e957454 100644 --- a/iosApp/iosApp/Task/AllTasksView.swift +++ b/iosApp/iosApp/Task/AllTasksView.swift @@ -198,242 +198,6 @@ struct AllTasksView: View { } } -/// Dynamic task column view that adapts based on the column configuration -struct DynamicTaskColumnView: View { - let column: TaskColumn - let onEditTask: (TaskDetail) -> Void - let onCancelTask: (Int32) -> Void - let onUncancelTask: (Int32) -> Void - let onMarkInProgress: (Int32) -> Void - let onCompleteTask: (TaskDetail) -> Void - let onArchiveTask: (Int32) -> Void - let onUnarchiveTask: (Int32) -> Void - - // Get icon from API response, with fallback - private var columnIcon: String { - column.icons["ios"] ?? "list.bullet" - } - - private var columnColor: Color { - Color(hex: column.color) ?? .primary - } - - var body: some View { - VStack(spacing: 0) { - ScrollView { - VStack(spacing: 16) { - // Header - HStack(spacing: 8) { - Image(systemName: columnIcon) - .font(.headline) - .foregroundColor(columnColor) - - Text(column.displayName) - .font(.headline) - .foregroundColor(columnColor) - - Spacer() - - Text("\(column.count)") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.white) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(columnColor) - .cornerRadius(12) - } - - if column.tasks.isEmpty { - VStack(spacing: 8) { - Image(systemName: columnIcon) - .font(.system(size: 40)) - .foregroundColor(columnColor.opacity(0.3)) - - Text("No tasks") - .font(.caption) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity) - .padding(.top, 40) - } else { - ForEach(column.tasks, id: \.id) { task in - DynamicTaskCard( - task: task, - buttonTypes: column.buttonTypes, - onEdit: { onEditTask(task) }, - onCancel: { onCancelTask(task.id) }, - onUncancel: { onUncancelTask(task.id) }, - onMarkInProgress: { onMarkInProgress(task.id) }, - onComplete: { onCompleteTask(task) }, - onArchive: { onArchiveTask(task.id) }, - onUnarchive: { onUnarchiveTask(task.id) } - ) - } - } - } - } - } - } -} - -/// Task card that dynamically renders buttons based on the column's button types -struct DynamicTaskCard: View { - let task: TaskDetail - let buttonTypes: [String] - let onEdit: () -> Void - let onCancel: () -> Void - let onUncancel: () -> Void - let onMarkInProgress: () -> Void - let onComplete: () -> Void - let onArchive: () -> Void - let onUnarchive: () -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - VStack(alignment: .leading, spacing: 4) { - Text(task.title) - .font(.headline) - .foregroundColor(.primary) - - if let status = task.status { - StatusBadge(status: status.name) - } - } - - Spacer() - - PriorityBadge(priority: task.priority.name) - } - - if let description = task.description_, !description.isEmpty { - Text(description) - .font(.subheadline) - .foregroundColor(.secondary) - .lineLimit(2) - } - - HStack { - Label(task.frequency.displayName, systemImage: "repeat") - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - if let due_date = task.dueDate { - Label(formatDate(due_date), systemImage: "calendar") - .font(.caption) - .foregroundColor(.secondary) - } - } - - if task.completions.count > 0 { - Divider() - - VStack(alignment: .leading, spacing: 8) { - HStack { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - Text("Completions (\(task.completions.count))") - .font(.caption) - .fontWeight(.semibold) - } - - ForEach(task.completions, id: \.id) { completion in - CompletionCardView(completion: completion) - } - } - } - - // Render buttons based on buttonTypes array - VStack(spacing: 8) { - ForEach(Array(buttonTypes.enumerated()), id: \.offset) { index, buttonType in - renderButton(for: buttonType) - } - } - } - .padding(16) - .background(Color(.systemBackground)) - .cornerRadius(12) - .shadow(color: Color.black.opacity(0.05), radius: 3, x: 0, y: 2) - } - - private func formatDate(_ dateString: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - if let date = formatter.date(from: dateString) { - formatter.dateStyle = .medium - return formatter.string(from: date) - } - return dateString - } - - @ViewBuilder - private func renderButton(for buttonType: String) -> some View { - switch buttonType { - case "mark_in_progress": - MarkInProgressButton( - taskId: task.id, - onCompletion: onMarkInProgress, - onError: { error in - print("Error marking in progress: \(error)") - } - ) - case "complete": - CompleteTaskButton( - taskId: task.id, - onCompletion: onComplete, - onError: { error in - print("Error completing task: \(error)") - } - ) - case "edit": - EditTaskButton( - taskId: task.id, - onCompletion: onEdit, - onError: { error in - print("Error editing task: \(error)") - } - ) - case "cancel": - CancelTaskButton( - taskId: task.id, - onCompletion: onCancel, - onError: { error in - print("Error cancelling task: \(error)") - } - ) - case "uncancel": - UncancelTaskButton( - taskId: task.id, - onCompletion: onUncancel, - onError: { error in - print("Error restoring task: \(error)") - } - ) - case "archive": - ArchiveTaskButton( - taskId: task.id, - onCompletion: onArchive, - onError: { error in - print("Error archiving task: \(error)") - } - ) - case "unarchive": - UnarchiveTaskButton( - taskId: task.id, - onCompletion: onUnarchive, - onError: { error in - print("Error unarchiving task: \(error)") - } - ) - default: - EmptyView() - } - } -} - // Extension to apply corner radius to specific corners extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { diff --git a/iosApp/iosApp/Task/CompleteTaskView.swift b/iosApp/iosApp/Task/CompleteTaskView.swift index 44c5b54..2a121bd 100644 --- a/iosApp/iosApp/Task/CompleteTaskView.swift +++ b/iosApp/iosApp/Task/CompleteTaskView.swift @@ -310,72 +310,3 @@ extension KotlinByteArray { } } -// Image Thumbnail View Component -struct ImageThumbnailView: View { - let image: UIImage - let onRemove: () -> Void - - var body: some View { - ZStack(alignment: .topTrailing) { - Image(uiImage: image) - .resizable() - .scaledToFill() - .frame(width: 100, height: 100) - .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) - .overlay { - RoundedRectangle(cornerRadius: 12, style: .continuous) - .strokeBorder(.quaternary, lineWidth: 1) - } - - Button(action: onRemove) { - Image(systemName: "xmark.circle.fill") - .font(.title3) - .foregroundStyle(.white) - .background { - Circle() - .fill(.black.opacity(0.6)) - .padding(4) - } - } - .offset(x: 8, y: -8) - } - } -} - -// Camera Picker View Component -struct CameraPickerView: UIViewControllerRepresentable { - @Environment(\.dismiss) var dismiss - let onImageCaptured: (UIImage) -> Void - - func makeUIViewController(context: Context) -> UIImagePickerController { - let picker = UIImagePickerController() - picker.sourceType = .camera - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { - let parent: CameraPickerView - - init(_ parent: CameraPickerView) { - self.parent = parent - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - if let image = info[.originalImage] as? UIImage { - parent.onImageCaptured(image) - } - parent.dismiss() - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - parent.dismiss() - } - } -}