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:
Trey t
2025-11-10 11:38:17 -06:00
parent 29b4c99f08
commit 77a118a6f7
23 changed files with 780 additions and 747 deletions

View File

@@ -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 {

View File

@@ -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()
}
}
}