Refactor iOS and Android views into separate files
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user