Migrate iOS app to system colors and improve UI/UX

- Remove AppColors struct, migrate to iOS system colors throughout
- Redesign ContractorFormSheet to use native SwiftUI Form components
- Add color-coded icons to contractor form sections
- Improve dark mode contrast for task cards
- Add background colors to document detail fields
- Fix text alignment issues in ContractorDetailView
- Make task completion lists expandable/collapsible by default
- Clear app badge on launch and when app becomes active
- Update button styling with proper gradients and shadows
- Improve form field focus states and accessibility

🤖 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-13 22:22:52 -06:00
parent 29c136d612
commit a2de0f3454
29 changed files with 475 additions and 593 deletions

View File

@@ -77,7 +77,7 @@ fun DocumentsScreen(
Icon(
if (showActiveOnly) Icons.Default.CheckCircle else Icons.Default.CheckCircleOutline,
"Filter active",
tint = if (showActiveOnly) Color(0xFF10B981) else LocalContentColor.current
tint = if (showActiveOnly) MaterialTheme.colorScheme.secondary else LocalContentColor.current
)
}
}
@@ -89,7 +89,7 @@ fun DocumentsScreen(
Icons.Default.FilterList,
"Filters",
tint = if (selectedCategory != null || selectedDocType != null)
Color(0xFF3B82F6) else LocalContentColor.current
MaterialTheme.colorScheme.primary else LocalContentColor.current
)
}

View File

@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.tt.mycrib.MyCrib</string>
<string>group.com.tt.mycrib.MyCribDev</string>
</array>
</dict>
</plist>

View File

@@ -394,7 +394,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCribDev;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -423,7 +423,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib.MyCrib;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCribDev.MyCribDev;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -457,7 +457,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib.MyCrib;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCribDev.MyCribDev;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -663,7 +663,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib;
PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCribDev;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@@ -10,12 +10,12 @@ struct ContractorCard: View {
// Avatar
ZStack {
Circle()
.fill(AppColors.primary.opacity(0.1))
.fill(.blue.opacity(0.1))
.frame(width: 56, height: 56)
Image(systemName: "person.fill")
.font(.title2)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
// Content
@@ -24,13 +24,13 @@ struct ContractorCard: View {
HStack(spacing: AppSpacing.xxs) {
Text(contractor.name)
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
.lineLimit(1)
if contractor.isFavorite {
Image(systemName: "star.fill")
.font(.caption)
.foregroundColor(AppColors.warning)
.foregroundColor(.orange)
}
}
@@ -38,7 +38,7 @@ struct ContractorCard: View {
if let company = contractor.company {
Text(company)
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.lineLimit(1)
}
@@ -48,21 +48,21 @@ struct ContractorCard: View {
if let specialty = contractor.specialty {
Label(specialty, systemImage: "wrench.and.screwdriver")
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
// Rating
if let rating = contractor.averageRating, rating.doubleValue > 0 {
Label(String(format: "%.1f", rating.doubleValue), systemImage: "star.fill")
.font(.caption.weight(.medium))
.foregroundColor(AppColors.warning)
.foregroundColor(.orange)
}
// Task count
if contractor.taskCount > 0 {
Label("\(contractor.taskCount) tasks", systemImage: "checkmark.circle")
.font(.caption.weight(.medium))
.foregroundColor(AppColors.success)
.foregroundColor(.green)
}
}
}
@@ -73,17 +73,17 @@ struct ContractorCard: View {
Button(action: onToggleFavorite) {
Image(systemName: contractor.isFavorite ? "star.fill" : "star")
.font(.title3)
.foregroundColor(contractor.isFavorite ? AppColors.warning : AppColors.textTertiary)
.foregroundColor(contractor.isFavorite ? .orange : Color(.tertiaryLabel))
}
.buttonStyle(PlainButtonStyle())
// Chevron
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
}

View File

@@ -12,7 +12,7 @@ struct ContractorDetailView: View {
var body: some View {
ZStack {
AppColors.background.ignoresSafeArea()
Color(.systemGroupedBackground).ignoresSafeArea()
if viewModel.isLoading {
ProgressView()
@@ -29,24 +29,24 @@ struct ContractorDetailView: View {
// Avatar
ZStack {
Circle()
.fill(AppColors.primary.opacity(0.1))
.fill(.blue.opacity(0.1))
.frame(width: 80, height: 80)
Image(systemName: "person.fill")
.font(.system(size: 40))
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
// Name
Text(contractor.name)
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
// Company
if let company = contractor.company {
Text(company)
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
// Specialty Badge
@@ -59,8 +59,8 @@ struct ContractorDetailView: View {
}
.padding(.horizontal, AppSpacing.sm)
.padding(.vertical, AppSpacing.xxs)
.background(AppColors.primary.opacity(0.1))
.foregroundColor(AppColors.primary)
.background(.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(AppRadius.full)
}
@@ -69,41 +69,41 @@ struct ContractorDetailView: View {
HStack(spacing: AppSpacing.xxs) {
ForEach(0..<5) { index in
Image(systemName: index < Int(rating.doubleValue) ? "star.fill" : "star")
.foregroundColor(AppColors.warning)
.foregroundColor(.orange)
.font(.caption)
}
Text(String(format: "%.1f", rating.doubleValue))
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
}
if contractor.taskCount > 0 {
Text("\(contractor.taskCount) completed tasks")
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
}
}
.padding(AppSpacing.lg)
.frame(maxWidth: .infinity)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
// Contact Information
DetailSection(title: "Contact Information") {
DetailRow(icon: "phone", label: "Phone", value: contractor.phone, iconColor: AppColors.primary)
DetailRow(icon: "phone", label: "Phone", value: contractor.phone, iconColor: .blue)
if let email = contractor.email {
DetailRow(icon: "envelope", label: "Email", value: email, iconColor: AppColors.accent)
DetailRow(icon: "envelope", label: "Email", value: email, iconColor: .purple)
}
if let secondaryPhone = contractor.secondaryPhone {
DetailRow(icon: "phone", label: "Secondary Phone", value: secondaryPhone, iconColor: AppColors.success)
DetailRow(icon: "phone", label: "Secondary Phone", value: secondaryPhone, iconColor: .green)
}
if let website = contractor.website {
DetailRow(icon: "globe", label: "Website", value: website, iconColor: AppColors.warning)
DetailRow(icon: "globe", label: "Website", value: website, iconColor: .orange)
}
}
@@ -111,7 +111,7 @@ struct ContractorDetailView: View {
if contractor.licenseNumber != nil {
DetailSection(title: "Business Details") {
if let licenseNumber = contractor.licenseNumber {
DetailRow(icon: "doc.badge", label: "License Number", value: licenseNumber, iconColor: AppColors.primary)
DetailRow(icon: "doc.badge", label: "License Number", value: licenseNumber, iconColor: .blue)
}
}
}
@@ -130,7 +130,7 @@ struct ContractorDetailView: View {
icon: "mappin.circle",
label: "Location",
value: addressComponents.joined(separator: "\n"),
iconColor: AppColors.error
iconColor: .red
)
}
}
@@ -141,7 +141,8 @@ struct ContractorDetailView: View {
DetailSection(title: "Notes") {
Text(notes)
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(AppSpacing.md)
}
}
@@ -150,11 +151,11 @@ struct ContractorDetailView: View {
DetailSection(title: "Task History") {
HStack {
Image(systemName: "checkmark.circle")
.foregroundColor(AppColors.success)
.foregroundColor(.green)
Spacer()
Text("\(contractor.taskCount) completed tasks")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
.padding(AppSpacing.md)
}
@@ -188,7 +189,7 @@ struct ContractorDetailView: View {
}
} label: {
Image(systemName: "ellipsis.circle")
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
}
}
@@ -236,13 +237,13 @@ struct DetailSection<Content: View>: View {
VStack(alignment: .leading, spacing: AppSpacing.sm) {
Text(title)
.font(.headline)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
.padding(.horizontal, AppSpacing.md)
VStack(spacing: 0) {
content()
}
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
}
@@ -254,7 +255,7 @@ struct DetailRow: View {
let icon: String
let label: String
let value: String
var iconColor: Color = AppColors.textSecondary
var iconColor: Color = Color(.secondaryLabel)
var body: some View {
HStack(alignment: .top, spacing: AppSpacing.sm) {
@@ -265,11 +266,11 @@ struct DetailRow: View {
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
Text(label)
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Text(value)
.font(.body)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
}
Spacer()

View File

@@ -47,38 +47,235 @@ struct ContractorFormSheet: View {
var body: some View {
NavigationStack {
ZStack {
AppColors.background.ignoresSafeArea()
ScrollView {
VStack(spacing: AppSpacing.lg) {
basicInformationSection
contactInformationSection
businessDetailsSection
addressSection
notesSection
favoriteToggle
errorMessage
Form {
// Basic Information
Section {
HStack {
Image(systemName: "person")
.foregroundColor(.blue)
.frame(width: 24)
TextField("Name", text: $name)
.focused($focusedField, equals: .name)
}
HStack {
Image(systemName: "building.2")
.foregroundColor(.purple)
.frame(width: 24)
TextField("Company", text: $company)
.focused($focusedField, equals: .company)
}
} header: {
Text("Basic Information")
}
// Contact Information
Section {
HStack {
Image(systemName: "phone.fill")
.foregroundColor(.green)
.frame(width: 24)
TextField("Phone", text: $phone)
.keyboardType(.phonePad)
.focused($focusedField, equals: .phone)
}
HStack {
Image(systemName: "envelope.fill")
.foregroundColor(.orange)
.frame(width: 24)
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.focused($focusedField, equals: .email)
}
HStack {
Image(systemName: "phone.badge.plus")
.foregroundColor(.green)
.frame(width: 24)
TextField("Secondary Phone", text: $secondaryPhone)
.keyboardType(.phonePad)
.focused($focusedField, equals: .secondaryPhone)
}
} header: {
Text("Contact Information")
} footer: {
Text("Required: Name and Phone")
.font(.caption)
}
// Business Details
Section {
Button(action: { showingSpecialtyPicker = true }) {
HStack {
Image(systemName: "wrench.and.screwdriver")
.foregroundColor(.blue)
.frame(width: 24)
Text(specialty.isEmpty ? "Specialty" : specialty)
.foregroundColor(specialty.isEmpty ? Color(.placeholderText) : Color(.label))
Spacer()
Image(systemName: "chevron.down")
.font(.caption)
.foregroundColor(Color(.tertiaryLabel))
}
}
HStack {
Image(systemName: "doc.badge")
.foregroundColor(.purple)
.frame(width: 24)
TextField("License Number", text: $licenseNumber)
.focused($focusedField, equals: .licenseNumber)
}
HStack {
Image(systemName: "globe")
.foregroundColor(.blue)
.frame(width: 24)
TextField("Website", text: $website)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.focused($focusedField, equals: .website)
}
} header: {
Text("Business Details")
}
// Address
Section {
HStack {
Image(systemName: "location.fill")
.foregroundColor(.red)
.frame(width: 24)
TextField("Street Address", text: $address)
.focused($focusedField, equals: .address)
}
HStack {
Image(systemName: "building.2.crop.circle")
.foregroundColor(.blue)
.frame(width: 24)
TextField("City", text: $city)
.focused($focusedField, equals: .city)
}
HStack(spacing: AppSpacing.sm) {
HStack {
Image(systemName: "map")
.foregroundColor(.green)
.frame(width: 24)
TextField("State", text: $state)
.focused($focusedField, equals: .state)
}
Divider()
.frame(height: 24)
TextField("ZIP", text: $zipCode)
.keyboardType(.numberPad)
.focused($focusedField, equals: .zipCode)
.frame(maxWidth: 100)
}
} header: {
Text("Address")
}
// Notes
Section {
HStack(alignment: .top) {
Image(systemName: "note.text")
.foregroundColor(.orange)
.frame(width: 24)
.padding(.top, 8)
TextEditor(text: $notes)
.frame(height: 100)
.focused($focusedField, equals: .notes)
}
} header: {
Text("Notes")
} footer: {
Text("Private notes about this contractor")
.font(.caption)
}
// Favorite
Section {
Toggle(isOn: $isFavorite) {
Label("Mark as Favorite", systemImage: "star.fill")
.foregroundColor(isFavorite ? .orange : Color(.label))
}
.tint(.orange)
}
// Error Message
if let error = viewModel.errorMessage {
Section {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.red)
Text(error)
.font(.callout)
.foregroundColor(.red)
}
}
.padding(AppSpacing.md)
}
}
.navigationTitle(contractor == nil ? "Add Contractor" : "Edit Contractor")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
cancelButton
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
saveButton
ToolbarItem(placement: .confirmationAction) {
Button(action: saveContractor) {
if viewModel.isCreating || viewModel.isUpdating {
ProgressView()
} else {
Text(contractor == nil ? "Add" : "Save")
.bold()
}
}
.disabled(!canSave || viewModel.isCreating || viewModel.isUpdating)
}
}
.sheet(isPresented: $showingSpecialtyPicker) {
SpecialtyPickerView(
selectedSpecialty: $specialty,
specialties: specialties
)
NavigationStack {
List {
ForEach(specialties, id: \.self) { spec in
Button(action: {
specialty = spec
showingSpecialtyPicker = false
}) {
HStack {
Text(spec)
.foregroundColor(Color(.label))
Spacer()
if specialty == spec {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
.navigationTitle("Select Specialty")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
showingSpecialtyPicker = false
}
}
}
}
.presentationDetents([.medium, .large])
}
.onAppear {
loadContractorData()
@@ -87,229 +284,6 @@ struct ContractorFormSheet: View {
}
}
// MARK: - Toolbar Items
private var cancelButton: some View {
Button("Cancel") {
dismiss()
}
.foregroundColor(AppColors.textSecondary)
}
private var saveButton: some View {
Button(action: saveContractor) {
if viewModel.isCreating || viewModel.isUpdating {
ProgressView()
} else {
Text(contractor == nil ? "Add" : "Save")
.foregroundColor(canSave ? AppColors.primary : AppColors.textTertiary)
}
}
.disabled(!canSave || viewModel.isCreating || viewModel.isUpdating)
}
// MARK: - Form Sections
private var basicInformationSection: some View {
VStack(spacing: AppSpacing.sm) {
SectionHeader(title: "Basic Information")
FormTextField(
title: "Name *",
text: $name,
icon: "person",
focused: $focusedField,
field: .name
)
FormTextField(
title: "Company",
text: $company,
icon: "building.2",
focused: $focusedField,
field: .company
)
}
}
private var contactInformationSection: some View {
VStack(spacing: AppSpacing.sm) {
SectionHeader(title: "Contact Information")
FormTextField(
title: "Phone *",
text: $phone,
icon: "phone",
keyboardType: .phonePad,
focused: $focusedField,
field: .phone
)
FormTextField(
title: "Email",
text: $email,
icon: "envelope",
keyboardType: .emailAddress,
focused: $focusedField,
field: .email
)
FormTextField(
title: "Secondary Phone",
text: $secondaryPhone,
icon: "phone",
keyboardType: .phonePad,
focused: $focusedField,
field: .secondaryPhone
)
}
}
private var businessDetailsSection: some View {
VStack(spacing: AppSpacing.sm) {
SectionHeader(title: "Business Details")
specialtyPickerButton
FormTextField(
title: "License Number",
text: $licenseNumber,
icon: "doc.badge",
focused: $focusedField,
field: .licenseNumber
)
FormTextField(
title: "Website",
text: $website,
icon: "globe",
keyboardType: .URL,
focused: $focusedField,
field: .website
)
}
}
private var specialtyPickerButton: some View {
Button(action: { showingSpecialtyPicker = true }) {
HStack {
Image(systemName: "wrench.and.screwdriver")
.foregroundColor(AppColors.textSecondary)
.frame(width: 20)
Text(specialty.isEmpty ? "Specialty" : specialty)
.foregroundColor(specialty.isEmpty ? AppColors.textTertiary : AppColors.textPrimary)
Spacer()
Image(systemName: "chevron.down")
.font(.caption)
.foregroundColor(AppColors.textTertiary)
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(AppColors.border, lineWidth: 1)
)
}
}
private var addressSection: some View {
VStack(spacing: AppSpacing.sm) {
SectionHeader(title: "Address")
FormTextField(
title: "Street Address",
text: $address,
icon: "mappin",
focused: $focusedField,
field: .address
)
HStack(spacing: AppSpacing.sm) {
FormTextField(
title: "City",
text: $city,
focused: $focusedField,
field: .city
)
FormTextField(
title: "State",
text: $state,
focused: $focusedField,
field: .state
)
.frame(maxWidth: 100)
}
FormTextField(
title: "ZIP Code",
text: $zipCode,
keyboardType: .numberPad,
focused: $focusedField,
field: .zipCode
)
}
}
private var notesSection: some View {
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
SectionHeader(title: "Notes")
HStack {
Image(systemName: "note.text")
.foregroundColor(AppColors.textSecondary)
.frame(width: 20)
Text("Private Notes")
.font(.footnote.weight(.medium))
.foregroundColor(AppColors.textSecondary)
}
TextEditor(text: $notes)
.frame(height: 100)
.padding(AppSpacing.sm)
.background(AppColors.surface)
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(AppColors.border, lineWidth: 1)
)
.focused($focusedField, equals: .notes)
}
}
private var favoriteToggle: some View {
Toggle(isOn: $isFavorite) {
HStack {
Image(systemName: "star.fill")
.foregroundColor(isFavorite ? AppColors.warning : AppColors.textSecondary)
Text("Mark as Favorite")
.font(.body)
.foregroundColor(AppColors.textPrimary)
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.cornerRadius(AppRadius.md)
}
@ViewBuilder
private var errorMessage: some View {
if let error = viewModel.errorMessage {
Text(error)
.font(.callout)
.foregroundColor(AppColors.error)
.padding(AppSpacing.sm)
.frame(maxWidth: .infinity)
.background(AppColors.error.opacity(0.1))
.cornerRadius(AppRadius.md)
}
}
// MARK: - Data Loading
private func loadContractorData() {
@@ -399,98 +373,3 @@ struct ContractorFormSheet: View {
}
}
}
// MARK: - Section Header
struct SectionHeader: View {
let title: String
var body: some View {
HStack {
Text(title)
.font(.headline)
.foregroundColor(AppColors.textPrimary)
Spacer()
}
}
}
// MARK: - Form Text Field
struct FormTextField: View {
let title: String
@Binding var text: String
var icon: String? = nil
var keyboardType: UIKeyboardType = .default
var focused: FocusState<ContractorFormField?>.Binding
var field: ContractorFormField
var body: some View {
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
if let icon = icon {
HStack {
Image(systemName: icon)
.foregroundColor(AppColors.textSecondary)
.frame(width: 20)
Text(title)
.font(.footnote.weight(.medium))
.foregroundColor(AppColors.textSecondary)
}
} else {
Text(title)
.font(.footnote.weight(.medium))
.foregroundColor(AppColors.textSecondary)
}
TextField("", text: $text)
.keyboardType(keyboardType)
.autocapitalization(keyboardType == .emailAddress ? .none : .words)
.padding(AppSpacing.md)
.background(AppColors.surface)
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(AppColors.border, lineWidth: 1)
)
.focused(focused, equals: field)
}
}
}
// MARK: - Specialty Picker
struct SpecialtyPickerView: View {
@Environment(\.dismiss) private var dismiss
@Binding var selectedSpecialty: String
let specialties: [String]
var body: some View {
NavigationView {
List {
ForEach(specialties, id: \.self) { specialty in
Button(action: {
selectedSpecialty = specialty
dismiss()
}) {
HStack {
Text(specialty)
.foregroundColor(AppColors.textPrimary)
Spacer()
if selectedSpecialty == specialty {
Image(systemName: "checkmark")
.foregroundColor(AppColors.primary)
}
}
}
}
}
.navigationTitle("Select Specialty")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") {
dismiss()
}
}
}
}
}
}

View File

@@ -26,7 +26,7 @@ struct ContractorsListView: View {
var body: some View {
ZStack {
AppColors.background.ignoresSafeArea()
Color(.systemGroupedBackground).ignoresSafeArea()
VStack(spacing: 0) {
// Search Bar
@@ -115,7 +115,7 @@ struct ContractorsListView: View {
loadContractors()
}) {
Image(systemName: showFavoritesOnly ? "star.fill" : "star")
.foregroundColor(showFavoritesOnly ? AppColors.warning : AppColors.textSecondary)
.foregroundColor(showFavoritesOnly ? .orange : Color(.secondaryLabel))
}
// Specialty Filter
@@ -139,14 +139,14 @@ struct ContractorsListView: View {
}
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.foregroundColor(selectedSpecialty != nil ? AppColors.primary : AppColors.textSecondary)
.foregroundColor(selectedSpecialty != nil ? .blue : Color(.secondaryLabel))
}
// Add Button
Button(action: { showingAddSheet = true }) {
Image(systemName: "plus.circle.fill")
.font(.title2)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
}
}
@@ -201,7 +201,7 @@ struct SearchBar: View {
var body: some View {
HStack(spacing: AppSpacing.sm) {
Image(systemName: "magnifyingglass")
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
TextField(placeholder, text: $text)
.font(.body)
@@ -209,12 +209,12 @@ struct SearchBar: View {
if !text.isEmpty {
Button(action: { text = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
}
}
.padding(AppSpacing.sm)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
}
@@ -242,8 +242,8 @@ struct FilterChip: View {
}
.padding(.horizontal, AppSpacing.sm)
.padding(.vertical, AppSpacing.xxs)
.background(AppColors.primary.opacity(0.1))
.foregroundColor(AppColors.primary)
.background(.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(AppRadius.full)
}
}
@@ -256,16 +256,16 @@ struct EmptyContractorsView: View {
VStack(spacing: AppSpacing.md) {
Image(systemName: "person.badge.plus")
.font(.system(size: 64))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
Text(hasFilters ? "No contractors found" : "No contractors yet")
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
if !hasFilters {
Text("Add your first contractor to get started")
.font(.callout)
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
}
}
.padding(AppSpacing.xl)

View File

@@ -3,55 +3,6 @@ import SwiftUI
// MARK: - Design System
// Modern, sleek design system for MyCrib with Light and Dark mode support
struct AppColors {
// Primary Colors - Modern blue gradient
static let primary = Color(hex: "2563EB") ?? .blue
static let primaryLight = Color(hex: "3B82F6") ?? .blue
static let primaryDark = Color(hex: "1E40AF") ?? .blue
// Accent Colors
static let accent = Color(hex: "8B5CF6") ?? .purple
static let accentLight = Color(hex: "A78BFA") ?? .purple
// Semantic Colors
static let success = Color(hex: "10B981") ?? .green
static let warning = Color(hex: "F59E0B") ?? .orange
static let error = Color(hex: "EF4444") ?? .red
static let info = Color(hex: "3B82F6") ?? .blue
// Adaptive Neutral Colors - Automatically adapt to light/dark mode
static let background = Color(uiColor: .systemGroupedBackground)
static let surface = Color(uiColor: .secondarySystemGroupedBackground)
static let surfaceSecondary = Color(uiColor: .tertiarySystemGroupedBackground)
static let textPrimary = Color(uiColor: .label)
static let textSecondary = Color(uiColor: .secondaryLabel)
static let textTertiary = Color(uiColor: .tertiaryLabel)
static let border = Color(uiColor: .separator)
static let borderLight = Color(uiColor: .opaqueSeparator)
// Task Status Colors
static let taskUpcoming = Color(hex: "3B82F6") ?? .blue
static let taskInProgress = Color(hex: "F59E0B") ?? .orange
static let taskCompleted = Color(hex: "10B981") ?? .green
static let taskCanceled = Color(hex: "6B7280") ?? .gray
static let taskArchived = Color(hex: "9CA3AF") ?? .gray
// Gradient
static let primaryGradient = LinearGradient(
colors: [primary, primaryLight],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
static let accentGradient = LinearGradient(
colors: [accent, accentLight],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
}
struct AppSpacing {
static let xxs: CGFloat = 4
static let xs: CGFloat = 8
@@ -102,7 +53,7 @@ struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: shadow.color, radius: shadow.radius, x: shadow.x, y: shadow.y)
}
@@ -118,7 +69,7 @@ struct PrimaryButtonStyle: ButtonStyle {
.frame(maxWidth: .infinity)
.frame(height: 56)
.background(
configuration.isPressed ? AppColors.primaryDark : AppColors.primary
configuration.isPressed ? .blue : .blue
)
.cornerRadius(AppRadius.md)
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
@@ -130,10 +81,10 @@ struct SecondaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
.frame(maxWidth: .infinity)
.frame(height: 56)
.background(AppColors.surfaceSecondary)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
@@ -144,11 +95,11 @@ struct TextFieldStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding(AppSpacing.md)
.background(AppColors.surfaceSecondary)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(AppColors.borderLight, lineWidth: 1)
.stroke(Color(.opaqueSeparator), lineWidth: 1)
)
}
}

View File

@@ -8,8 +8,8 @@ struct DocumentCard: View {
switch document.documentType {
case "warranty": return .blue
case "manual": return .purple
case "receipt": return AppColors.success
case "inspection": return AppColors.warning
case "receipt": return .green
case "inspection": return .orange
default: return .gray
}
}
@@ -41,13 +41,13 @@ struct DocumentCard: View {
Text(document.title)
.font(.title3.weight(.semibold))
.fontWeight(.bold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
.lineLimit(1)
if let description = document.description_, !description.isEmpty {
Text(description)
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.lineLimit(2)
}
@@ -63,7 +63,7 @@ struct DocumentCard: View {
if let fileSize = document.fileSize {
Text(formatFileSize(Int(fileSize)))
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
}
}
@@ -71,11 +71,11 @@ struct DocumentCard: View {
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.font(.system(size: 14))
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
}

View File

@@ -9,15 +9,15 @@ struct EmptyStateView: View {
VStack(spacing: AppSpacing.md) {
Image(systemName: icon)
.font(.system(size: 64))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Text(title)
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Text(message)
.font(.body)
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
.multilineTextAlignment(.center)
}
.padding(AppSpacing.lg)

View File

@@ -10,10 +10,10 @@ struct WarrantyCard: View {
var statusColor: Color {
if !document.isActive { return .gray }
if daysUntilExpiration < 0 { return AppColors.error }
if daysUntilExpiration < 30 { return AppColors.warning }
if daysUntilExpiration < 0 { return .red }
if daysUntilExpiration < 30 { return .orange }
if daysUntilExpiration < 90 { return .yellow }
return AppColors.success
return .green
}
var statusText: String {
@@ -31,11 +31,11 @@ struct WarrantyCard: View {
Text(document.title)
.font(.title3.weight(.semibold))
.fontWeight(.bold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text(document.itemName ?? "")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
Spacer()
@@ -58,11 +58,11 @@ struct WarrantyCard: View {
VStack(alignment: .leading, spacing: 2) {
Text("Provider")
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Text(document.provider ?? "N/A")
.font(.body)
.fontWeight(.medium)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
}
Spacer()
@@ -70,11 +70,11 @@ struct WarrantyCard: View {
VStack(alignment: .trailing, spacing: 2) {
Text("Expires")
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Text(document.endDate ?? "N/A")
.font(.body)
.fontWeight(.medium)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
}
}
@@ -96,7 +96,7 @@ struct WarrantyCard: View {
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
}

View File

@@ -400,6 +400,9 @@ struct DocumentDetailView: View {
.font(.body)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(12)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(8)
}
private func getStatusColor(isActive: Bool, daysUntilExpiration: Int32) -> Color {

View File

@@ -28,7 +28,7 @@ struct DocumentsWarrantiesView: View {
var body: some View {
ZStack {
AppColors.background.ignoresSafeArea()
Color(.systemGroupedBackground).ignoresSafeArea()
VStack(spacing: 0) {
// Segmented Control for Tabs
@@ -104,7 +104,7 @@ struct DocumentsWarrantiesView: View {
loadWarranties()
}) {
Image(systemName: showActiveOnly ? "checkmark.circle.fill" : "checkmark.circle")
.foregroundColor(showActiveOnly ? AppColors.success : AppColors.textSecondary)
.foregroundColor(showActiveOnly ? .green : Color(.secondaryLabel))
}
}
@@ -149,7 +149,7 @@ struct DocumentsWarrantiesView: View {
}
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.foregroundColor((selectedCategory != nil || selectedDocType != nil) ? AppColors.primary : AppColors.textSecondary)
.foregroundColor((selectedCategory != nil || selectedDocType != nil) ? .blue : Color(.secondaryLabel))
}
// Add Button
@@ -158,7 +158,7 @@ struct DocumentsWarrantiesView: View {
}) {
Image(systemName: "plus.circle.fill")
.font(.title2)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
}
}

View File

@@ -8,7 +8,7 @@ struct HomeScreenView: View {
var body: some View {
NavigationView {
ZStack {
AppColors.background
Color(.systemGroupedBackground)
.ignoresSafeArea()
if viewModel.isLoading {
@@ -17,7 +17,7 @@ struct HomeScreenView: View {
.scaleEffect(1.2)
Text("Loading...")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
} else {
ScrollView(showsIndicators: false) {
@@ -27,11 +27,11 @@ struct HomeScreenView: View {
Text("Hello!")
.font(.title.weight(.bold))
.fontWeight(.bold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text("Welcome to MyCrib")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, AppSpacing.md)
@@ -80,7 +80,7 @@ struct HomeScreenView: View {
Image(systemName: "rectangle.portrait.and.arrow.right")
.font(.system(size: 18, weight: .semibold))
}
.foregroundColor(AppColors.error)
.foregroundColor(.red)
}
}
}

View File

@@ -15,6 +15,8 @@
</array>
</dict>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>

View File

@@ -25,7 +25,7 @@ struct LoginView: View {
private var buttonBackgroundColor: Color {
if viewModel.isLoading || !isFormValid {
return AppColors.textTertiary
return Color(.tertiaryLabel)
}
return .clear
}
@@ -38,7 +38,7 @@ struct LoginView: View {
NavigationView {
ZStack {
// Background gradient
AppColors.background
Color(.systemGroupedBackground)
.ignoresSafeArea()
ScrollView {
@@ -51,9 +51,9 @@ struct LoginView: View {
// App Icon with gradient
ZStack {
Circle()
.fill(AppColors.primaryGradient)
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 100, height: 100)
.shadow(color: AppColors.primary.opacity(0.3), radius: 20, y: 10)
.shadow(color: .blue.opacity(0.3), radius: 20, y: 10)
Image(systemName: "house.fill")
.font(.system(size: 50, weight: .semibold))
@@ -63,11 +63,11 @@ struct LoginView: View {
VStack(spacing: AppSpacing.xs) {
Text("Welcome Back")
.font(.title2.weight(.bold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text("Sign in to manage your properties")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
}
@@ -77,11 +77,11 @@ struct LoginView: View {
VStack(alignment: .leading, spacing: AppSpacing.xs) {
Text("Email or Username")
.font(.subheadline.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
HStack(spacing: AppSpacing.sm) {
Image(systemName: "envelope.fill")
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
.frame(width: 20)
TextField("Enter your email", text: $viewModel.username)
@@ -98,13 +98,13 @@ struct LoginView: View {
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(focusedField == .username ? AppColors.primary : AppColors.border, lineWidth: 1.5)
.stroke(focusedField == .username ? .blue : Color(.separator), lineWidth: 1.5)
)
.shadow(color: focusedField == .username ? AppColors.primary.opacity(0.1) : .clear, radius: 8)
.shadow(color: focusedField == .username ? .blue.opacity(0.1) : .clear, radius: 8)
.animation(.easeInOut(duration: 0.2), value: focusedField)
}
@@ -112,11 +112,11 @@ struct LoginView: View {
VStack(alignment: .leading, spacing: AppSpacing.xs) {
Text("Password")
.font(.subheadline.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
HStack(spacing: AppSpacing.sm) {
Image(systemName: "lock.fill")
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
.frame(width: 20)
Group {
@@ -143,18 +143,18 @@ struct LoginView: View {
isPasswordVisible.toggle()
}) {
Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill")
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
.frame(width: 20)
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.md)
.overlay(
RoundedRectangle(cornerRadius: AppRadius.md)
.stroke(focusedField == .password ? AppColors.primary : AppColors.border, lineWidth: 1.5)
.stroke(focusedField == .password ? .blue : Color(.separator), lineWidth: 1.5)
)
.shadow(color: focusedField == .password ? AppColors.primary.opacity(0.1) : .clear, radius: 8)
.shadow(color: focusedField == .password ? .blue.opacity(0.1) : .clear, radius: 8)
.animation(.easeInOut(duration: 0.2), value: focusedField)
.onChange(of: viewModel.password) { _, _ in
viewModel.clearError()
@@ -168,21 +168,21 @@ struct LoginView: View {
showPasswordReset = true
}
.font(.subheadline.weight(.medium))
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
// Error Message
if let errorMessage = viewModel.errorMessage {
HStack(spacing: AppSpacing.sm) {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(AppColors.error)
.foregroundColor(.red)
Text(errorMessage)
.font(.callout)
.foregroundColor(AppColors.error)
.foregroundColor(.red)
Spacer()
}
.padding(AppSpacing.md)
.background(AppColors.error.opacity(0.1))
.background(.red.opacity(0.1))
.cornerRadius(AppRadius.md)
}
@@ -196,18 +196,18 @@ struct LoginView: View {
HStack(spacing: AppSpacing.xs) {
Text("Don't have an account?")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
Button("Sign Up") {
showingRegister = true
}
.font(.body)
.fontWeight(.semibold)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
}
.padding(AppSpacing.xl)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.xxl)
.shadow(color: .black.opacity(0.08), radius: 20, y: 10)
.padding(.horizontal, AppSpacing.lg)
@@ -285,18 +285,17 @@ struct LoginView: View {
.background(loginButtonBackground)
.cornerRadius(AppRadius.md)
.shadow(
color: shouldShowShadow ? AppColors.primary.opacity(0.3) : .clear,
color: shouldShowShadow ? .blue.opacity(0.3) : .clear,
radius: 10,
y: 5
)
}
@ViewBuilder
private var loginButtonBackground: some View {
private var loginButtonBackground: AnyShapeStyle {
if viewModel.isLoading || !isFormValid {
AppColors.textTertiary
AnyShapeStyle(Color(.tertiaryLabel))
} else {
AppColors.primaryGradient
AnyShapeStyle(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
}
}
}

View File

@@ -16,9 +16,23 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
await PushNotificationManager.shared.requestNotificationPermission()
}
// Clear badge when app launches
Task { @MainActor in
PushNotificationManager.shared.clearBadge()
}
return true
}
// MARK: - App Lifecycle
func applicationDidBecomeActive(_ application: UIApplication) {
// Clear badge when app becomes active
Task { @MainActor in
PushNotificationManager.shared.clearBadge()
}
}
// MARK: - Remote Notifications
func application(

View File

@@ -8,7 +8,7 @@ struct ResidencesListView: View {
var body: some View {
ZStack {
AppColors.background
Color(.systemGroupedBackground)
.ignoresSafeArea()
if viewModel.myResidences == nil && viewModel.isLoading {
@@ -17,7 +17,7 @@ struct ResidencesListView: View {
.scaleEffect(1.2)
Text("Loading properties...")
.font(.body)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
} else if let error = viewModel.errorMessage {
ErrorView(message: error) {
@@ -39,10 +39,10 @@ struct ResidencesListView: View {
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
Text("Your Properties")
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text("\(response.residences.count) \(response.residences.count == 1 ? "property" : "properties")")
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
Spacer()
}
@@ -77,7 +77,7 @@ struct ResidencesListView: View {
}) {
Image(systemName: "person.badge.plus")
.font(.system(size: 18, weight: .semibold))
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
Button(action: {
@@ -85,7 +85,7 @@ struct ResidencesListView: View {
}) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 22, weight: .semibold))
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
}
}
}

View File

@@ -10,9 +10,9 @@ struct HomeNavigationCard: View {
// Icon with gradient background
ZStack {
RoundedRectangle(cornerRadius: AppRadius.md)
.fill(AppColors.primaryGradient)
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 60, height: 60)
.shadow(color: AppColors.primary.opacity(0.3), radius: 8, y: 4)
.shadow(color: .blue.opacity(0.3), radius: 8, y: 4)
Image(systemName: icon)
.font(.system(size: 28, weight: .semibold))
@@ -24,11 +24,11 @@ struct HomeNavigationCard: View {
Text(title)
.font(.title3.weight(.semibold))
.fontWeight(.semibold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text(subtitle)
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
Spacer()
@@ -36,10 +36,10 @@ struct HomeNavigationCard: View {
// Chevron
Image(systemName: "chevron.right")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
}
.padding(AppSpacing.lg)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
}

View File

@@ -11,7 +11,7 @@ struct OverviewCard: View {
HStack(spacing: AppSpacing.sm) {
ZStack {
Circle()
.fill(AppColors.primaryGradient)
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 44, height: 44)
Image(systemName: "chart.bar.fill")
@@ -21,7 +21,7 @@ struct OverviewCard: View {
Text("Overview")
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
}
Spacer()
}
@@ -32,7 +32,7 @@ struct OverviewCard: View {
icon: "house.fill",
value: "\(summary.totalResidences)",
label: "Properties",
color: AppColors.primary
color: .blue
)
Divider()
@@ -42,7 +42,7 @@ struct OverviewCard: View {
icon: "list.bullet",
value: "\(summary.totalTasks)",
label: "Total Tasks",
color: AppColors.info
color: .blue
)
Divider()
@@ -52,12 +52,12 @@ struct OverviewCard: View {
icon: "clock.fill",
value: "\(summary.totalPending)",
label: "Pending",
color: AppColors.warning
color: .orange
)
}
}
.padding(AppSpacing.xl)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.xl)
.shadow(color: AppShadow.lg.color, radius: AppShadow.lg.radius, x: AppShadow.lg.x, y: AppShadow.lg.y)
.padding(.horizontal, AppSpacing.md)

View File

@@ -4,7 +4,7 @@ struct StatView: View {
let icon: String
let value: String
let label: String
var color: Color = AppColors.primary
var color: Color = .blue
var body: some View {
VStack(spacing: AppSpacing.sm) {
@@ -21,11 +21,11 @@ struct StatView: View {
Text(value)
.font(.title2.weight(.semibold))
.fontWeight(.bold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Text(label)
.font(.footnote.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)

View File

@@ -10,9 +10,9 @@ struct ResidenceCard: View {
HStack(spacing: AppSpacing.sm) {
ZStack {
RoundedRectangle(cornerRadius: AppRadius.sm)
.fill(AppColors.primaryGradient)
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 44, height: 44)
.shadow(color: AppColors.primary.opacity(0.3), radius: 6, y: 3)
.shadow(color: .blue.opacity(0.3), radius: 6, y: 3)
Image(systemName: "house.fill")
.font(.system(size: 20, weight: .semibold))
@@ -23,12 +23,12 @@ struct ResidenceCard: View {
Text(residence.name)
.font(.title3.weight(.semibold))
.fontWeight(.bold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
.lineLimit(1)
Text(residence.propertyType)
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
.textCase(.uppercase)
.tracking(0.5)
}
@@ -38,11 +38,11 @@ struct ResidenceCard: View {
if residence.isPrimary {
ZStack {
Circle()
.fill(AppColors.accent.opacity(0.1))
.fill(.purple.opacity(0.1))
.frame(width: 32, height: 32)
Image(systemName: "star.fill")
.font(.system(size: 14, weight: .bold))
.foregroundColor(AppColors.accent)
.foregroundColor(.purple)
}
}
}
@@ -52,19 +52,19 @@ struct ResidenceCard: View {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "mappin.circle.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
Text(residence.streetAddress)
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "location.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
Text("\(residence.city), \(residence.stateProvince)")
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
}
.padding(.vertical, AppSpacing.xs)
@@ -77,26 +77,26 @@ struct ResidenceCard: View {
icon: "list.bullet",
value: "\(residence.taskSummary.total)",
label: "Tasks",
color: AppColors.info
color: .blue
)
TaskStatChip(
icon: "checkmark.circle.fill",
value: "\(residence.taskSummary.completed)",
label: "Done",
color: AppColors.success
color: .green
)
TaskStatChip(
icon: "clock.fill",
value: "\(residence.taskSummary.pending)",
label: "Pending",
color: AppColors.warning
color: .orange
)
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
}
@@ -138,5 +138,5 @@ struct ResidenceCard: View {
updatedAt: "2024-01-01T00:00:00Z"
))
.padding()
.background(AppColors.background)
.background(Color(.systemGroupedBackground))
}

View File

@@ -36,7 +36,7 @@ struct CompletionCardView: View {
HStack(alignment: .top, spacing: 6) {
Image(systemName: "wrench.and.screwdriver")
.font(.caption2)
.foregroundColor(AppColors.primary)
.foregroundColor(.blue)
VStack(alignment: .leading, spacing: 2) {
Text("By: \(contractorDetails.name)")

View File

@@ -13,6 +13,8 @@ struct DynamicTaskCard: View {
let onArchive: () -> Void
let onUnarchive: () -> Void
@State private var isCompletionsExpanded = false
var body: some View {
let _ = print("📋 DynamicTaskCard - Task: \(task.title), ButtonTypes: \(buttonTypes)")
@@ -20,7 +22,7 @@ struct DynamicTaskCard: View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(task.title)
.font(.headline)
.font(.title3)
.foregroundColor(.primary)
if let status = task.status {
@@ -64,10 +66,23 @@ struct DynamicTaskCard: View {
Text("Completions (\(task.completions.count))")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.primary)
Spacer()
Image(systemName: isCompletionsExpanded ? "chevron.up" : "chevron.down")
.font(.caption)
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
isCompletionsExpanded.toggle()
}
}
ForEach(task.completions, id: \.id) { completion in
CompletionCardView(completion: completion)
if isCompletionsExpanded {
ForEach(task.completions, id: \.id) { completion in
CompletionCardView(completion: completion)
}
}
}
}
@@ -87,18 +102,22 @@ struct DynamicTaskCard: View {
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color.blue)
.foregroundColor(.white)
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 2)
)
}
.zIndex(10)
.menuOrder(.fixed)
}
}
.padding(16)
.background(Color(.systemBackground))
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(12)
.shadow(color: Color.black.opacity(0.05), radius: 3, x: 0, y: 2)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
.simultaneousGesture(TapGesture(), including: .subviews)
}

View File

@@ -21,10 +21,10 @@ struct PriorityBadge: View {
private var priorityColor: Color {
switch priority.lowercased() {
case "high": return AppColors.error
case "medium": return AppColors.warning
case "low": return AppColors.success
default: return AppColors.textTertiary
case "high": return .red
case "medium": return .orange
case "low": return .green
default: return Color(.tertiaryLabel)
}
}
}
@@ -36,5 +36,5 @@ struct PriorityBadge: View {
PriorityBadge(priority: "low")
}
.padding()
.background(AppColors.background)
.background(Color(.systemGroupedBackground))
}

View File

@@ -24,11 +24,11 @@ struct StatusBadge: View {
private var statusColor: Color {
switch status {
case "completed": return AppColors.success
case "in_progress": return AppColors.taskInProgress
case "pending": return AppColors.warning
case "cancelled": return AppColors.error
default: return AppColors.textTertiary
case "completed": return .green
case "in_progress": return .orange
case "pending": return .orange
case "cancelled": return .red
default: return Color(.tertiaryLabel)
}
}
}
@@ -41,5 +41,5 @@ struct StatusBadge: View {
StatusBadge(status: "cancelled")
}
.padding()
.background(AppColors.background)
.background(Color(.systemGroupedBackground))
}

View File

@@ -11,6 +11,8 @@ struct TaskCard: View {
let onArchive: (() -> Void)?
let onUnarchive: (() -> Void)?
@State private var isCompletionsExpanded = false
var body: some View {
VStack(alignment: .leading, spacing: AppSpacing.md) {
// Header
@@ -18,7 +20,7 @@ struct TaskCard: View {
VStack(alignment: .leading, spacing: AppSpacing.xs) {
Text(task.title)
.font(.title3.weight(.semibold))
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
.lineLimit(2)
if let status = task.status {
@@ -35,7 +37,7 @@ struct TaskCard: View {
if let description = task.description_, !description.isEmpty {
Text(description)
.font(.callout)
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
.lineLimit(3)
}
@@ -44,14 +46,14 @@ struct TaskCard: View {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "repeat")
.font(.system(size: 12, weight: .medium))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
Text(task.frequency.displayName)
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
.padding(.horizontal, AppSpacing.sm)
.padding(.vertical, AppSpacing.xxs)
.background(AppColors.surfaceSecondary)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.xs)
Spacer()
@@ -60,14 +62,14 @@ struct TaskCard: View {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "calendar")
.font(.system(size: 12, weight: .medium))
.foregroundColor(AppColors.textTertiary)
.foregroundColor(Color(.tertiaryLabel))
Text(formatDate(dueDate))
.font(.caption.weight(.medium))
.foregroundColor(AppColors.textSecondary)
.foregroundColor(Color(.secondaryLabel))
}
.padding(.horizontal, AppSpacing.sm)
.padding(.vertical, AppSpacing.xxs)
.background(AppColors.surfaceSecondary)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.xs)
}
}
@@ -81,20 +83,32 @@ struct TaskCard: View {
HStack(spacing: AppSpacing.xs) {
ZStack {
Circle()
.fill(AppColors.success.opacity(0.1))
.fill(Color.green.opacity(0.1))
.frame(width: 24, height: 24)
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(AppColors.success)
.foregroundColor(.green)
}
Text("Completions (\(task.completions.count))")
.font(.footnote.weight(.medium))
.fontWeight(.semibold)
.foregroundColor(AppColors.textPrimary)
.foregroundColor(Color(.label))
Spacer()
Image(systemName: isCompletionsExpanded ? "chevron.up" : "chevron.down")
.font(.caption)
.foregroundColor(Color(.secondaryLabel))
}
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
isCompletionsExpanded.toggle()
}
}
ForEach(task.completions, id: \.id) { completion in
CompletionCardView(completion: completion)
if isCompletionsExpanded {
ForEach(task.completions, id: \.id) { completion in
CompletionCardView(completion: completion)
}
}
}
}
@@ -113,8 +127,8 @@ struct TaskCard: View {
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.foregroundColor(AppColors.warning)
.background(AppColors.warning.opacity(0.1))
.foregroundColor(.orange)
.background(Color.orange.opacity(0.1))
.cornerRadius(AppRadius.md)
}
}
@@ -131,7 +145,7 @@ struct TaskCard: View {
.frame(maxWidth: .infinity)
.frame(height: 44)
.foregroundColor(.white)
.background(AppColors.success)
.background(.green)
.cornerRadius(AppRadius.md)
}
}
@@ -150,8 +164,8 @@ struct TaskCard: View {
}
.frame(maxWidth: .infinity)
.frame(height: 36)
.foregroundColor(AppColors.primary)
.background(AppColors.surfaceSecondary)
.foregroundColor(.blue)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.sm)
}
@@ -165,8 +179,8 @@ struct TaskCard: View {
}
.frame(maxWidth: .infinity)
.frame(height: 36)
.foregroundColor(AppColors.error)
.background(AppColors.error.opacity(0.1))
.foregroundColor(.red)
.background(Color.red.opacity(0.1))
.cornerRadius(AppRadius.sm)
}
} else if let onUncancel = onUncancel {
@@ -180,7 +194,7 @@ struct TaskCard: View {
.frame(maxWidth: .infinity)
.frame(height: 36)
.foregroundColor(.white)
.background(AppColors.primary)
.background(.blue)
.cornerRadius(AppRadius.sm)
}
}
@@ -197,8 +211,8 @@ struct TaskCard: View {
}
.frame(maxWidth: .infinity)
.frame(height: 36)
.foregroundColor(AppColors.primary)
.background(AppColors.surfaceSecondary)
.foregroundColor(.blue)
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.sm)
}
}
@@ -213,8 +227,8 @@ struct TaskCard: View {
}
.frame(maxWidth: .infinity)
.frame(height: 36)
.foregroundColor(AppColors.textSecondary)
.background(AppColors.surfaceSecondary)
.foregroundColor(Color(.secondaryLabel))
.background(Color(.tertiarySystemGroupedBackground))
.cornerRadius(AppRadius.sm)
}
}
@@ -222,7 +236,7 @@ struct TaskCard: View {
}
}
.padding(AppSpacing.md)
.background(AppColors.surface)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(AppRadius.lg)
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
}
@@ -271,5 +285,5 @@ struct TaskCard: View {
)
}
.padding()
.background(AppColors.background)
.background(Color(.systemGroupedBackground))
}

View File

@@ -393,7 +393,7 @@ struct ContractorPickerView: View {
Spacer()
if selectedContractor == nil {
Image(systemName: "checkmark")
.foregroundStyle(AppColors.primary)
.foregroundStyle(.blue)
}
}
}
@@ -441,7 +441,7 @@ struct ContractorPickerView: View {
if selectedContractor?.id == contractor.id {
Image(systemName: "checkmark")
.foregroundStyle(AppColors.primary)
.foregroundStyle(.blue)
}
}
}

View File

@@ -6,7 +6,7 @@
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.tt.mycrib.MyCrib</string>
<string>group.com.tt.mycrib.MyCribDev</string>
</array>
</dict>
</plist>