Implement custom 5-color design system across entire iOS app
Apply consistent branding colors (BlueGreen, Cerulean, BrightAmber, PrimaryScarlet, cream backgrounds) to all screens, components, buttons, icons, and text throughout the app. Update all Form/List views with proper list row backgrounds to ensure visual consistency with card-based layouts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"filename" : "logo_primary.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
6
iosApp/iosApp/Assets.xcassets/Colors/Contents.json
Normal file
6
iosApp/iosApp/Assets.xcassets/Colors/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.031",
|
||||
"green" : "0.784",
|
||||
"red" : "0.941"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.031",
|
||||
"green" : "0.784",
|
||||
"red" : "0.941"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.816",
|
||||
"green" : "0.945",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.161",
|
||||
"green" : "0.098",
|
||||
"red" : "0.039"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.247",
|
||||
"green" : "0.184",
|
||||
"red" : "0.102"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.102",
|
||||
"green" : "0.110",
|
||||
"red" : "0.867"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.267",
|
||||
"green" : "0.267",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.765",
|
||||
"green" : "0.627",
|
||||
"red" : "0.027"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.765",
|
||||
"green" : "0.627",
|
||||
"red" : "0.027"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.533",
|
||||
"green" : "0.404",
|
||||
"red" : "0.031"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.745",
|
||||
"green" : "0.624",
|
||||
"red" : "0.039"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.533",
|
||||
"green" : "0.404",
|
||||
"red" : "0.031"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.816",
|
||||
"green" : "0.945",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.600",
|
||||
"blue" : "0.533",
|
||||
"green" : "0.404",
|
||||
"red" : "0.031"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.700",
|
||||
"blue" : "0.816",
|
||||
"green" : "0.945",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,14 @@ struct TaskSummaryCard: View {
|
||||
Text("Tasks")
|
||||
.font(.headline)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
ForEach(filteredCategories, id: \.name) { category in
|
||||
TaskCategoryRow(category: category)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color(.systemBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(12)
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
|
||||
}
|
||||
@@ -55,14 +56,14 @@ struct TaskCategoryRow: View {
|
||||
.frame(width: 32, height: 32)
|
||||
|
||||
Image(systemName: category.icons.ios)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
}
|
||||
|
||||
// Category name
|
||||
Text(category.displayName)
|
||||
.font(.body)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -70,7 +71,7 @@ struct TaskCategoryRow: View {
|
||||
Text("\(category.count)")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 4)
|
||||
.background(categoryColor)
|
||||
|
||||
@@ -10,12 +10,12 @@ struct ContractorCard: View {
|
||||
// Avatar
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(.blue.opacity(0.1))
|
||||
.fill(Color.appPrimary.opacity(0.1))
|
||||
.frame(width: 56, height: 56)
|
||||
|
||||
Image(systemName: "person.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
|
||||
// Content
|
||||
@@ -24,13 +24,13 @@ struct ContractorCard: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
Text(contractor.name)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.lineLimit(1)
|
||||
|
||||
if contractor.isFavorite {
|
||||
Image(systemName: "star.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ struct ContractorCard: View {
|
||||
if let company = contractor.company {
|
||||
Text(company)
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.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(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
// Rating
|
||||
if let rating = contractor.averageRating, rating.doubleValue > 0 {
|
||||
Label(String(format: "%.1f", rating.doubleValue), systemImage: "star.fill")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
|
||||
// Task count
|
||||
if contractor.taskCount > 0 {
|
||||
Label("\(contractor.taskCount) tasks", systemImage: "checkmark.circle")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,17 +73,17 @@ struct ContractorCard: View {
|
||||
Button(action: onToggleFavorite) {
|
||||
Image(systemName: contractor.isFavorite ? "star.fill" : "star")
|
||||
.font(.title3)
|
||||
.foregroundColor(contractor.isFavorite ? .orange : Color(.tertiaryLabel))
|
||||
.foregroundColor(contractor.isFavorite ? Color.appAccent : Color.appTextSecondary.opacity(0.7))
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// Chevron
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ struct ContractorDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground).ignoresSafeArea()
|
||||
Color.appBackgroundPrimary.ignoresSafeArea()
|
||||
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
@@ -29,24 +29,24 @@ struct ContractorDetailView: View {
|
||||
// Avatar
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(.blue.opacity(0.1))
|
||||
.fill(Color.appPrimary.opacity(0.1))
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
Image(systemName: "person.fill")
|
||||
.font(.system(size: 40))
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
|
||||
// Name
|
||||
Text(contractor.name)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
// Company
|
||||
if let company = contractor.company {
|
||||
Text(company)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
// Specialty Badge
|
||||
@@ -59,8 +59,8 @@ struct ContractorDetailView: View {
|
||||
}
|
||||
.padding(.horizontal, AppSpacing.sm)
|
||||
.padding(.vertical, AppSpacing.xxs)
|
||||
.background(.blue.opacity(0.1))
|
||||
.foregroundColor(.blue)
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.cornerRadius(AppRadius.full)
|
||||
}
|
||||
|
||||
@@ -69,43 +69,43 @@ struct ContractorDetailView: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
ForEach(0..<5) { index in
|
||||
Image(systemName: index < Int(rating.doubleValue) ? "star.fill" : "star")
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(String(format: "%.1f", rating.doubleValue))
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
if contractor.taskCount > 0 {
|
||||
Text("\(contractor.taskCount) completed tasks")
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.lg)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.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") {
|
||||
if let phone = contractor.phone {
|
||||
DetailRow(icon: "phone", label: "Phone", value: phone, iconColor: .blue)
|
||||
DetailRow(icon: "phone", label: "Phone", value: phone, iconColor: Color.appPrimary)
|
||||
}
|
||||
|
||||
if let email = contractor.email {
|
||||
DetailRow(icon: "envelope", label: "Email", value: email, iconColor: .purple)
|
||||
DetailRow(icon: "envelope", label: "Email", value: email, iconColor: Color.appPrimary)
|
||||
}
|
||||
|
||||
if let secondaryPhone = contractor.secondaryPhone {
|
||||
DetailRow(icon: "phone", label: "Secondary Phone", value: secondaryPhone, iconColor: .green)
|
||||
DetailRow(icon: "phone", label: "Secondary Phone", value: secondaryPhone, iconColor: Color.appAccent)
|
||||
}
|
||||
|
||||
if let website = contractor.website {
|
||||
DetailRow(icon: "globe", label: "Website", value: website, iconColor: .orange)
|
||||
DetailRow(icon: "globe", label: "Website", value: website, iconColor: Color.appAccent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,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: .blue)
|
||||
DetailRow(icon: "doc.badge", label: "License Number", value: licenseNumber, iconColor: Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ struct ContractorDetailView: View {
|
||||
icon: "mappin.circle",
|
||||
label: "Location",
|
||||
value: addressComponents.joined(separator: "\n"),
|
||||
iconColor: .red
|
||||
iconColor: Color.appError
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ struct ContractorDetailView: View {
|
||||
DetailSection(title: "Notes") {
|
||||
Text(notes)
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(AppSpacing.md)
|
||||
}
|
||||
@@ -153,11 +153,11 @@ struct ContractorDetailView: View {
|
||||
DetailSection(title: "Task History") {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
Spacer()
|
||||
Text("\(contractor.taskCount) completed tasks")
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
}
|
||||
@@ -191,7 +191,7 @@ struct ContractorDetailView: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,13 +243,13 @@ struct DetailSection<Content: View>: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.sm) {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.padding(.horizontal, AppSpacing.md)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
content()
|
||||
}
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
|
||||
}
|
||||
@@ -272,11 +272,11 @@ struct DetailRow: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
||||
Text(label)
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Text(value)
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -52,7 +52,7 @@ struct ContractorFormSheet: View {
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "person")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Name", text: $name)
|
||||
.focused($focusedField, equals: .name)
|
||||
@@ -60,7 +60,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "building.2")
|
||||
.foregroundColor(.purple)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Company", text: $company)
|
||||
.focused($focusedField, equals: .company)
|
||||
@@ -70,14 +70,15 @@ struct ContractorFormSheet: View {
|
||||
} footer: {
|
||||
Text("Required: Name")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Contact Information
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "phone.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Phone", text: $phone)
|
||||
.keyboardType(.phonePad)
|
||||
@@ -86,7 +87,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "envelope.fill")
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("Email", text: $email)
|
||||
.keyboardType(.emailAddress)
|
||||
@@ -97,7 +98,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "phone.badge.plus")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Secondary Phone", text: $secondaryPhone)
|
||||
.keyboardType(.phonePad)
|
||||
@@ -106,28 +107,29 @@ struct ContractorFormSheet: View {
|
||||
} header: {
|
||||
Text("Contact Information")
|
||||
} footer: {
|
||||
|
||||
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Business Details
|
||||
Section {
|
||||
Button(action: { showingSpecialtyPicker = true }) {
|
||||
HStack {
|
||||
Image(systemName: "wrench.and.screwdriver")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
Text(specialty.isEmpty ? "Specialty" : specialty)
|
||||
.foregroundColor(specialty.isEmpty ? Color(.placeholderText) : Color(.label))
|
||||
.foregroundColor(specialty.isEmpty ? Color.appTextSecondary.opacity(0.5) : Color.appTextPrimary)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image(systemName: "doc.badge")
|
||||
.foregroundColor(.purple)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("License Number", text: $licenseNumber)
|
||||
.focused($focusedField, equals: .licenseNumber)
|
||||
@@ -135,7 +137,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "globe")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("Website", text: $website)
|
||||
.keyboardType(.URL)
|
||||
@@ -146,12 +148,13 @@ struct ContractorFormSheet: View {
|
||||
} header: {
|
||||
Text("Business Details")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Address
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "location.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.frame(width: 24)
|
||||
TextField("Street Address", text: $address)
|
||||
.focused($focusedField, equals: .address)
|
||||
@@ -159,7 +162,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "building.2.crop.circle")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("City", text: $city)
|
||||
.focused($focusedField, equals: .city)
|
||||
@@ -168,7 +171,7 @@ struct ContractorFormSheet: View {
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
HStack {
|
||||
Image(systemName: "map")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("State", text: $state)
|
||||
.focused($focusedField, equals: .state)
|
||||
@@ -185,12 +188,13 @@ struct ContractorFormSheet: View {
|
||||
} header: {
|
||||
Text("Address")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Notes
|
||||
Section {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "note.text")
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
.padding(.top, 8)
|
||||
|
||||
@@ -204,29 +208,35 @@ struct ContractorFormSheet: View {
|
||||
Text("Private notes about this contractor")
|
||||
.font(.caption)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Favorite
|
||||
Section {
|
||||
Toggle(isOn: $isFavorite) {
|
||||
Label("Mark as Favorite", systemImage: "star.fill")
|
||||
.foregroundColor(isFavorite ? .orange : Color(.label))
|
||||
.foregroundColor(isFavorite ? Color.appAccent : Color.appTextPrimary)
|
||||
}
|
||||
.tint(.orange)
|
||||
.tint(Color.appAccent)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Error Message
|
||||
if let error = viewModel.errorMessage {
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
Text(error)
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle(contractor == nil ? "Add Contractor" : "Edit Contractor")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
@@ -258,16 +268,18 @@ struct ContractorFormSheet: View {
|
||||
}) {
|
||||
HStack {
|
||||
Text(spec)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Spacer()
|
||||
if specialty == spec {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Select Specialty")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -26,7 +26,7 @@ struct ContractorsListView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground).ignoresSafeArea()
|
||||
Color.appBackgroundPrimary.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
// Search Bar
|
||||
@@ -115,7 +115,7 @@ struct ContractorsListView: View {
|
||||
loadContractors()
|
||||
}) {
|
||||
Image(systemName: showFavoritesOnly ? "star.fill" : "star")
|
||||
.foregroundColor(showFavoritesOnly ? .orange : Color(.secondaryLabel))
|
||||
.foregroundColor(showFavoritesOnly ? Color.appAccent : Color.appTextSecondary)
|
||||
}
|
||||
|
||||
// Specialty Filter
|
||||
@@ -139,14 +139,14 @@ struct ContractorsListView: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
.foregroundColor(selectedSpecialty != nil ? .blue : Color(.secondaryLabel))
|
||||
.foregroundColor(selectedSpecialty != nil ? Color.appPrimary : Color.appTextSecondary)
|
||||
}
|
||||
|
||||
// Add Button
|
||||
Button(action: { showingAddSheet = true }) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Contractor.addButton)
|
||||
}
|
||||
@@ -206,7 +206,7 @@ struct SearchBar: View {
|
||||
var body: some View {
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
TextField(placeholder, text: $text)
|
||||
.font(.body)
|
||||
@@ -214,12 +214,12 @@ struct SearchBar: View {
|
||||
if !text.isEmpty {
|
||||
Button(action: { text = "" }) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.sm)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.shadow(color: AppShadow.sm.color, radius: AppShadow.sm.radius, x: AppShadow.sm.x, y: AppShadow.sm.y)
|
||||
}
|
||||
@@ -247,8 +247,8 @@ struct FilterChip: View {
|
||||
}
|
||||
.padding(.horizontal, AppSpacing.sm)
|
||||
.padding(.vertical, AppSpacing.xxs)
|
||||
.background(.blue.opacity(0.1))
|
||||
.foregroundColor(.blue)
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.cornerRadius(AppRadius.full)
|
||||
}
|
||||
}
|
||||
@@ -261,16 +261,16 @@ struct EmptyContractorsView: View {
|
||||
VStack(spacing: AppSpacing.md) {
|
||||
Image(systemName: "person.badge.plus")
|
||||
.font(.system(size: 64))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
|
||||
Text(hasFilters ? "No contractors found" : "No contractors yet")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
if !hasFilters {
|
||||
Text("Add your first contractor to get started")
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.xl)
|
||||
|
||||
@@ -3,6 +3,49 @@ import SwiftUI
|
||||
// MARK: - Design System
|
||||
// Modern, sleek design system for MyCrib with Light and Dark mode support
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
extension Color {
|
||||
// MARK: - Semantic Colors (Use These in UI)
|
||||
static let appPrimary = Color("Primary")
|
||||
static let appSecondary = Color("Secondary")
|
||||
static let appAccent = Color("Accent")
|
||||
static let appBackgroundPrimary = Color("BackgroundPrimary")
|
||||
static let appBackgroundSecondary = Color("BackgroundSecondary")
|
||||
static let appError = Color("Error")
|
||||
static let appTextPrimary = Color("TextPrimary")
|
||||
static let appTextSecondary = Color("TextSecondary")
|
||||
static let appTextOnPrimary = Color("TextOnPrimary")
|
||||
|
||||
// MARK: - Hex Support
|
||||
init?(hex: String) {
|
||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
var int: UInt64 = 0
|
||||
Scanner(string: hex).scanHexInt64(&int)
|
||||
let a, r, g, b: UInt64
|
||||
switch hex.count {
|
||||
case 3: // RGB (12-bit)
|
||||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
||||
case 6: // RGB (24-bit)
|
||||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
||||
case 8: // ARGB (32-bit)
|
||||
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
.sRGB,
|
||||
red: Double(r) / 255,
|
||||
green: Double(g) / 255,
|
||||
blue: Double(b) / 255,
|
||||
opacity: Double(a) / 255
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spacing
|
||||
|
||||
struct AppSpacing {
|
||||
static let xxs: CGFloat = 4
|
||||
static let xs: CGFloat = 8
|
||||
@@ -53,7 +96,7 @@ struct CardStyle: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: shadow.color, radius: shadow.radius, x: shadow.x, y: shadow.y)
|
||||
}
|
||||
@@ -65,11 +108,11 @@ struct PrimaryButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(.appTextOnPrimary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 56)
|
||||
.background(
|
||||
configuration.isPressed ? .blue : .blue
|
||||
configuration.isPressed ? Color.appPrimary.opacity(0.8) : Color.appPrimary
|
||||
)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
|
||||
@@ -81,10 +124,10 @@ struct SecondaryButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.font(.headline)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(.appPrimary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 56)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.cornerRadius(AppRadius.md)
|
||||
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
|
||||
@@ -95,11 +138,11 @@ struct TextFieldStyle: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppRadius.md)
|
||||
.stroke(Color(.opaqueSeparator), lineWidth: 1)
|
||||
.stroke(Color.appTextSecondary.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,31 +159,3 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Color Extension for Hex Support
|
||||
|
||||
extension Color {
|
||||
init?(hex: String) {
|
||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
var int: UInt64 = 0
|
||||
Scanner(string: hex).scanHexInt64(&int)
|
||||
let a, r, g, b: UInt64
|
||||
switch hex.count {
|
||||
case 3: // RGB (12-bit)
|
||||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
||||
case 6: // RGB (24-bit)
|
||||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
||||
case 8: // ARGB (32-bit)
|
||||
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
.sRGB,
|
||||
red: Double(r) / 255,
|
||||
green: Double(g) / 255,
|
||||
blue: Double(b) / 255,
|
||||
opacity: Double(a) / 255
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ struct DocumentCard: View {
|
||||
|
||||
var typeColor: Color {
|
||||
switch document.documentType {
|
||||
case "warranty": return .blue
|
||||
case "manual": return .purple
|
||||
case "receipt": return .green
|
||||
case "inspection": return .orange
|
||||
default: return .gray
|
||||
case "warranty": return Color.appPrimary
|
||||
case "manual": return Color.appSecondary
|
||||
case "receipt": return Color.appAccent
|
||||
case "inspection": return Color.appAccent
|
||||
default: return Color.appTextSecondary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ struct DocumentCard: View {
|
||||
Text(document.title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.lineLimit(1)
|
||||
|
||||
if let description = document.description_, !description.isEmpty {
|
||||
Text(description)
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ struct DocumentCard: View {
|
||||
if let fileSize = document.fileSize {
|
||||
Text(formatFileSize(Int(fileSize)))
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,11 +71,11 @@ struct DocumentCard: View {
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ struct EmptyStateView: View {
|
||||
VStack(spacing: AppSpacing.md) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 64))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Text(title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Text(message)
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(AppSpacing.lg)
|
||||
|
||||
@@ -9,11 +9,11 @@ struct WarrantyCard: View {
|
||||
}
|
||||
|
||||
var statusColor: Color {
|
||||
if !document.isActive { return .gray }
|
||||
if daysUntilExpiration < 0 { return .red }
|
||||
if daysUntilExpiration < 30 { return .orange }
|
||||
if daysUntilExpiration < 90 { return .yellow }
|
||||
return .green
|
||||
if !document.isActive { return Color.appTextSecondary }
|
||||
if daysUntilExpiration < 0 { return Color.appError }
|
||||
if daysUntilExpiration < 30 { return Color.appAccent }
|
||||
if daysUntilExpiration < 90 { return Color.appAccent.opacity(0.7) }
|
||||
return Color.appPrimary
|
||||
}
|
||||
|
||||
var statusText: String {
|
||||
@@ -31,11 +31,11 @@ struct WarrantyCard: View {
|
||||
Text(document.title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(document.itemName ?? "")
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -58,11 +58,11 @@ struct WarrantyCard: View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Provider")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text(document.provider ?? "N/A")
|
||||
.font(.body)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -70,11 +70,11 @@ struct WarrantyCard: View {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text("Expires")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text(document.endDate ?? "N/A")
|
||||
.font(.body)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,15 +88,15 @@ struct WarrantyCard: View {
|
||||
if let category = document.category {
|
||||
Text(getCategoryDisplayName(category))
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(hex: "374151"))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Color(hex: "E5E7EB"))
|
||||
.background(Color.appBackgroundSecondary.opacity(0.8))
|
||||
.cornerRadius(4)
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ struct DocumentFormView: View {
|
||||
if !itemNameError.isEmpty {
|
||||
Text(itemNameError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
TextField("Model Number (optional)", text: $modelNumber)
|
||||
@@ -129,7 +129,7 @@ struct DocumentFormView: View {
|
||||
if !providerError.isEmpty {
|
||||
Text(providerError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
TextField("Provider Contact (optional)", text: $providerContact)
|
||||
@@ -138,8 +138,9 @@ struct DocumentFormView: View {
|
||||
} footer: {
|
||||
Text("Required for warranties: Item Name and Provider")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section("Warranty Claims") {
|
||||
TextField("Claim Phone (optional)", text: $claimPhone)
|
||||
@@ -149,12 +150,14 @@ struct DocumentFormView: View {
|
||||
TextField("Claim Website (optional)", text: $claimWebsite)
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section("Warranty Dates") {
|
||||
TextField("Purchase Date (YYYY-MM-DD)", text: $purchaseDate)
|
||||
TextField("Warranty Start Date (YYYY-MM-DD)", text: $startDate)
|
||||
TextField("Warranty End Date (YYYY-MM-DD)", text: $endDate)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +184,7 @@ struct DocumentFormView: View {
|
||||
.frame(height: 200)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section("Photos") {
|
||||
@@ -200,6 +204,7 @@ struct DocumentFormView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -207,6 +212,9 @@ struct DocumentFormView: View {
|
||||
Form {
|
||||
formContent
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle(isEditMode ? (isWarranty ? "Edit Warranty" : "Edit Document") : (isWarranty ? "Add Warranty" : "Add Document"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
@@ -282,7 +290,7 @@ struct DocumentFormView: View {
|
||||
if !residenceError.isEmpty {
|
||||
Text(residenceError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -290,8 +298,9 @@ struct DocumentFormView: View {
|
||||
} footer: {
|
||||
Text("Required")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
// Document Type
|
||||
@@ -314,6 +323,7 @@ struct DocumentFormView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Basic Information
|
||||
Section {
|
||||
@@ -321,7 +331,7 @@ struct DocumentFormView: View {
|
||||
if !titleError.isEmpty {
|
||||
Text(titleError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
TextField("Description (optional)", text: $description, axis: .vertical)
|
||||
@@ -331,8 +341,9 @@ struct DocumentFormView: View {
|
||||
} footer: {
|
||||
Text("Required: Title")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Warranty-specific fields
|
||||
warrantySection
|
||||
@@ -347,6 +358,7 @@ struct DocumentFormView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
// Additional Information
|
||||
@@ -356,12 +368,14 @@ struct DocumentFormView: View {
|
||||
TextField("Notes (optional)", text: $notes, axis: .vertical)
|
||||
.lineLimit(3...6)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Active Status (Edit mode only)
|
||||
if isEditMode {
|
||||
Section {
|
||||
Toggle("Active", isOn: $isActive)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
// Photos
|
||||
|
||||
@@ -28,7 +28,7 @@ struct DocumentsWarrantiesView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground).ignoresSafeArea()
|
||||
Color.appBackgroundPrimary.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 ? .green : Color(.secondaryLabel))
|
||||
.foregroundColor(showActiveOnly ? Color.appPrimary : Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ struct DocumentsWarrantiesView: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
.foregroundColor((selectedCategory != nil || selectedDocType != nil) ? .blue : Color(.secondaryLabel))
|
||||
.foregroundColor((selectedCategory != nil || selectedDocType != nil) ? Color.appPrimary : Color.appTextSecondary)
|
||||
}
|
||||
|
||||
// Add Button
|
||||
@@ -158,7 +158,7 @@ struct DocumentsWarrantiesView: View {
|
||||
}) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ struct LoginView: View {
|
||||
|
||||
private var buttonBackgroundColor: Color {
|
||||
if viewModel.isLoading || !isFormValid {
|
||||
return Color(.tertiaryLabel)
|
||||
return Color.appTextSecondary
|
||||
}
|
||||
return .clear
|
||||
}
|
||||
@@ -39,7 +39,7 @@ struct LoginView: View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
// Background gradient
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
@@ -52,9 +52,9 @@ struct LoginView: View {
|
||||
// App Icon with gradient
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.frame(width: 100, height: 100)
|
||||
.shadow(color: .blue.opacity(0.3), radius: 20, y: 10)
|
||||
.shadow(color: Color.appPrimary.opacity(0.3), radius: 20, y: 10)
|
||||
|
||||
Image(systemName: "house.fill")
|
||||
.font(.system(size: 50, weight: .semibold))
|
||||
@@ -64,11 +64,11 @@ struct LoginView: View {
|
||||
VStack(spacing: AppSpacing.xs) {
|
||||
Text("Welcome Back")
|
||||
.font(.title2.weight(.bold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Sign in to manage your properties")
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +78,11 @@ struct LoginView: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xs) {
|
||||
Text("Email or Username")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "envelope.fill")
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.frame(width: 20)
|
||||
|
||||
TextField("Enter your email", text: $viewModel.username)
|
||||
@@ -100,13 +100,13 @@ struct LoginView: View {
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.usernameField)
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppRadius.md)
|
||||
.stroke(focusedField == .username ? .blue : Color(.separator), lineWidth: 1.5)
|
||||
.stroke(focusedField == .username ? Color.appPrimary : Color.appTextSecondary.opacity(0.3), lineWidth: 1.5)
|
||||
)
|
||||
.shadow(color: focusedField == .username ? .blue.opacity(0.1) : .clear, radius: 8)
|
||||
.shadow(color: focusedField == .username ? Color.appPrimary.opacity(0.1) : .clear, radius: 8)
|
||||
.animation(.easeInOut(duration: 0.2), value: focusedField)
|
||||
}
|
||||
|
||||
@@ -114,11 +114,11 @@ struct LoginView: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xs) {
|
||||
Text("Password")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.frame(width: 20)
|
||||
|
||||
Group {
|
||||
@@ -147,19 +147,19 @@ struct LoginView: View {
|
||||
isPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill")
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.frame(width: 20)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.passwordVisibilityToggle)
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppRadius.md)
|
||||
.stroke(focusedField == .password ? .blue : Color(.separator), lineWidth: 1.5)
|
||||
.stroke(focusedField == .password ? Color.appPrimary : Color.appTextSecondary.opacity(0.3), lineWidth: 1.5)
|
||||
)
|
||||
.shadow(color: focusedField == .password ? .blue.opacity(0.1) : .clear, radius: 8)
|
||||
.shadow(color: focusedField == .password ? Color.appPrimary.opacity(0.1) : .clear, radius: 8)
|
||||
.animation(.easeInOut(duration: 0.2), value: focusedField)
|
||||
.onChange(of: viewModel.password) { _, _ in
|
||||
viewModel.clearError()
|
||||
@@ -173,7 +173,7 @@ struct LoginView: View {
|
||||
showPasswordReset = true
|
||||
}
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.forgotPasswordButton)
|
||||
}
|
||||
|
||||
@@ -181,14 +181,14 @@ struct LoginView: View {
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "exclamationmark.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
Text(errorMessage)
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
Spacer()
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(.red.opacity(0.1))
|
||||
.background(Color.appError.opacity(0.1))
|
||||
.cornerRadius(AppRadius.md)
|
||||
}
|
||||
|
||||
@@ -203,19 +203,19 @@ struct LoginView: View {
|
||||
HStack(spacing: AppSpacing.xs) {
|
||||
Text("Don't have an account?")
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Button("Sign Up") {
|
||||
showingRegister = true
|
||||
}
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.signUpButton)
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.xl)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.xxl)
|
||||
.shadow(color: .black.opacity(0.08), radius: 20, y: 10)
|
||||
.padding(.horizontal, AppSpacing.lg)
|
||||
@@ -282,11 +282,11 @@ struct LoginView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 56)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.background(loginButtonBackground)
|
||||
.cornerRadius(AppRadius.md)
|
||||
.shadow(
|
||||
color: shouldShowShadow ? .blue.opacity(0.3) : .clear,
|
||||
color: shouldShowShadow ? Color.appPrimary.opacity(0.3) : .clear,
|
||||
radius: 10,
|
||||
y: 5
|
||||
)
|
||||
@@ -294,9 +294,9 @@ struct LoginView: View {
|
||||
|
||||
private var loginButtonBackground: AnyShapeStyle {
|
||||
if viewModel.isLoading || !isFormValid {
|
||||
AnyShapeStyle(Color(.tertiaryLabel))
|
||||
AnyShapeStyle(Color.appTextSecondary)
|
||||
} else {
|
||||
AnyShapeStyle(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
AnyShapeStyle(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ struct MainTabView: View {
|
||||
.tag(4)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.profileTab)
|
||||
}
|
||||
.tint(Color.appPrimary)
|
||||
.onChange(of: authManager.isAuthenticated) { _ in
|
||||
selectedTab = 0
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ struct ForgotPasswordView: View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "key.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
.padding(.vertical)
|
||||
|
||||
Text("Forgot Password?")
|
||||
@@ -22,7 +22,7 @@ struct ForgotPasswordView: View {
|
||||
|
||||
Text("Enter your email address and we'll send you a verification code")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -49,30 +49,33 @@ struct ForgotPasswordView: View {
|
||||
} footer: {
|
||||
Text("We'll send a 6-digit verification code to this address")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Error/Success Messages
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
Label {
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
} icon: {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
if let successMessage = viewModel.successMessage {
|
||||
Section {
|
||||
Label {
|
||||
Text(successMessage)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
// Send Code Button
|
||||
@@ -99,12 +102,16 @@ struct ForgotPasswordView: View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Back to Login")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Reset Password")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onAppear {
|
||||
|
||||
@@ -20,16 +20,17 @@ struct ResetPasswordView: View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "lock.rotation")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
.padding(.vertical)
|
||||
|
||||
Text("Set New Password")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Create a strong password to secure your account")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -42,30 +43,34 @@ struct ResetPasswordView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: viewModel.newPassword.count >= 8 ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(viewModel.newPassword.count >= 8 ? .green : .secondary)
|
||||
.foregroundColor(viewModel.newPassword.count >= 8 ? Color.appPrimary : Color.appTextSecondary)
|
||||
Text("At least 8 characters")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: hasLetter ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(hasLetter ? .green : .secondary)
|
||||
.foregroundColor(hasLetter ? Color.appPrimary : Color.appTextSecondary)
|
||||
Text("Contains letters")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: hasNumber ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(hasNumber ? .green : .secondary)
|
||||
.foregroundColor(hasNumber ? Color.appPrimary : Color.appTextSecondary)
|
||||
Text("Contains numbers")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: passwordsMatch ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(passwordsMatch ? .green : .secondary)
|
||||
.foregroundColor(passwordsMatch ? Color.appPrimary : Color.appTextSecondary)
|
||||
Text("Passwords match")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -97,7 +102,7 @@ struct ResetPasswordView: View {
|
||||
isNewPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isNewPasswordVisible ? "eye.slash.fill" : "eye.fill")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -133,7 +138,7 @@ struct ResetPasswordView: View {
|
||||
isConfirmPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isConfirmPasswordVisible ? "eye.slash.fill" : "eye.fill")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -149,10 +154,10 @@ struct ResetPasswordView: View {
|
||||
Section {
|
||||
Label {
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
} icon: {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,11 +166,11 @@ struct ResetPasswordView: View {
|
||||
Section {
|
||||
Label {
|
||||
Text(successMessage)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +209,9 @@ struct ResetPasswordView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Reset Password")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
|
||||
@@ -13,21 +13,22 @@ struct VerifyResetCodeView: View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "envelope.badge.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
.padding(.vertical)
|
||||
|
||||
Text("Check Your Email")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("We sent a 6-digit code to")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Text(viewModel.email)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
@@ -39,11 +40,13 @@ struct VerifyResetCodeView: View {
|
||||
Label {
|
||||
Text("Code expires in 15 minutes")
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
} icon: {
|
||||
Image(systemName: "clock.fill")
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Code Input Section
|
||||
Section {
|
||||
@@ -66,30 +69,33 @@ struct VerifyResetCodeView: View {
|
||||
} footer: {
|
||||
Text("Enter the 6-digit code from your email")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Error/Success Messages
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
Label {
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
} icon: {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
if let successMessage = viewModel.successMessage {
|
||||
Section {
|
||||
Label {
|
||||
Text(successMessage)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
// Verify Button
|
||||
@@ -110,13 +116,14 @@ struct VerifyResetCodeView: View {
|
||||
}
|
||||
.disabled(viewModel.code.count != 6 || viewModel.isLoading)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Help Section
|
||||
Section {
|
||||
VStack(spacing: 12) {
|
||||
Text("Didn't receive the code?")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Button(action: {
|
||||
// Clear code and go back to request new one
|
||||
@@ -131,13 +138,16 @@ struct VerifyResetCodeView: View {
|
||||
|
||||
Text("Check your spam folder if you don't see it")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Verify Code")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
|
||||
@@ -11,18 +11,20 @@ struct ProfileTabView: View {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("User Profile")
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Manage your account")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section("Account") {
|
||||
@@ -30,7 +32,7 @@ struct ProfileTabView: View {
|
||||
showingProfileEdit = true
|
||||
}) {
|
||||
Label("Edit Profile", systemImage: "person.crop.circle")
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
NavigationLink(destination: Text("Notifications")) {
|
||||
@@ -41,15 +43,17 @@ struct ProfileTabView: View {
|
||||
Label("Privacy", systemImage: "lock.shield")
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
Button(action: {
|
||||
showingLogoutAlert = true
|
||||
}) {
|
||||
Label("Log Out", systemImage: "rectangle.portrait.and.arrow.right")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Profile.logoutButton)
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -57,13 +61,17 @@ struct ProfileTabView: View {
|
||||
Text("MyCrib")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Version 1.0.0")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Profile")
|
||||
.sheet(isPresented: $showingProfileEdit) {
|
||||
ProfileView()
|
||||
|
||||
@@ -16,7 +16,7 @@ struct ProfileView: View {
|
||||
ProgressView()
|
||||
Text("Loading profile...")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
} else {
|
||||
@@ -25,11 +25,12 @@ struct ProfileView: View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
|
||||
Text("Profile Settings")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
@@ -57,6 +58,7 @@ struct ProfileView: View {
|
||||
} header: {
|
||||
Text("Personal Information")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
TextField("Email", text: $viewModel.email)
|
||||
@@ -73,29 +75,32 @@ struct ProfileView: View {
|
||||
} footer: {
|
||||
Text("Email is required and must be unique")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
if let successMessage = viewModel.successMessage {
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
Text(successMessage)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -113,7 +118,11 @@ struct ProfileView: View {
|
||||
}
|
||||
.disabled(viewModel.isLoading || viewModel.email.isEmpty)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Profile")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -18,7 +18,7 @@ struct RegisterView: View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "person.badge.plus")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
|
||||
Text("Join MyCrib")
|
||||
.font(.largeTitle)
|
||||
@@ -26,7 +26,7 @@ struct RegisterView: View {
|
||||
|
||||
Text("Start managing your properties today")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
@@ -57,6 +57,7 @@ struct RegisterView: View {
|
||||
} header: {
|
||||
Text("Account Information")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
SecureField("Password", text: $viewModel.password)
|
||||
@@ -79,17 +80,19 @@ struct RegisterView: View {
|
||||
} footer: {
|
||||
Text("Password must be secure")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -108,7 +111,11 @@ struct RegisterView: View {
|
||||
.disabled(viewModel.isLoading)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerButton)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Create Account")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -28,14 +28,16 @@ struct JoinResidenceView: View {
|
||||
Text("Enter Share Code")
|
||||
} footer: {
|
||||
Text("Enter the 6-character code shared with you to join a residence")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
if let error = viewModel.errorMessage {
|
||||
Section {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -54,7 +56,11 @@ struct JoinResidenceView: View {
|
||||
}
|
||||
.disabled(shareCode.count != 6 || viewModel.isLoading)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Join Residence")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ManageUsersView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
if isLoading {
|
||||
@@ -63,6 +63,9 @@ struct ManageUsersView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Manage Users")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -30,9 +30,9 @@ struct ResidenceDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
|
||||
mainContent
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
@@ -174,7 +174,7 @@ private extension ResidenceDetailView {
|
||||
ProgressView()
|
||||
Text("Loading residence...")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ private extension ResidenceDetailView {
|
||||
ProgressView("Loading tasks...")
|
||||
} else if let tasksError = tasksError {
|
||||
Text("Error loading tasks: \(tasksError)")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ private extension ResidenceDetailView {
|
||||
showDeleteConfirmation = true
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.foregroundStyle(.red)
|
||||
.foregroundStyle(Color.appError)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.deleteButton)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ struct ResidencesListView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
if viewModel.myResidences == nil && viewModel.isLoading {
|
||||
@@ -19,7 +19,7 @@ struct ResidencesListView: View {
|
||||
.scaleEffect(1.2)
|
||||
Text("Loading properties...")
|
||||
.font(.body)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
} else if let response = viewModel.myResidences {
|
||||
if response.residences.isEmpty {
|
||||
@@ -37,10 +37,10 @@ struct ResidencesListView: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
||||
Text("Your Properties")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text("\(response.residences.count) \(response.residences.count == 1 ? "property" : "properties")")
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
@@ -75,7 +75,7 @@ struct ResidencesListView: View {
|
||||
}) {
|
||||
Image(systemName: "person.badge.plus")
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
@@ -83,7 +83,7 @@ struct ResidencesListView: View {
|
||||
}) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.system(size: 22, weight: .semibold))
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.addButton)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ struct ResidenceFormView: View {
|
||||
if !nameError.isEmpty {
|
||||
Text(nameError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
Picker("Property Type", selection: $selectedPropertyType) {
|
||||
@@ -70,8 +70,9 @@ struct ResidenceFormView: View {
|
||||
} footer: {
|
||||
Text("Required: Name")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
TextField("Street Address", text: $streetAddress)
|
||||
@@ -100,6 +101,7 @@ struct ResidenceFormView: View {
|
||||
} header: {
|
||||
Text("Address")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section(header: Text("Property Features")) {
|
||||
HStack {
|
||||
@@ -139,6 +141,7 @@ struct ResidenceFormView: View {
|
||||
.focused($focusedField, equals: .yearBuilt)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.yearBuiltField)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section(header: Text("Additional Details")) {
|
||||
TextField("Description (optional)", text: $description, axis: .vertical)
|
||||
@@ -148,15 +151,20 @@ struct ResidenceFormView: View {
|
||||
Toggle("Primary Residence", isOn: $isPrimary)
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.isPrimaryToggle)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.font(.caption)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle(isEditMode ? "Edit Residence" : "Add Residence")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -7,11 +7,12 @@ struct LoginHeader: View {
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 80, height: 80)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
Text("MyCrib")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
.padding(.top, 60)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
@@ -7,15 +7,16 @@ struct RegisterHeader: View {
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 64, height: 64)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
Text("Join MyCrib")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Start managing your properties today")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
@@ -7,21 +7,21 @@ struct ErrorMessageView: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
|
||||
Text(message)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: onDismiss) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.red.opacity(0.1))
|
||||
.background(Color.appError.opacity(0.1))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@ struct ErrorView: View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.font(.system(size: 64))
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
|
||||
Text("Error: \(message)")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Button(action: retryAction) {
|
||||
Text("Retry")
|
||||
.padding(.horizontal, 32)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.appPrimary)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ struct HomeNavigationCard: View {
|
||||
// Icon with gradient background
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: AppRadius.md)
|
||||
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.frame(width: 60, height: 60)
|
||||
.shadow(color: .blue.opacity(0.3), radius: 8, y: 4)
|
||||
.shadow(color: Color.appPrimary.opacity(0.3), radius: 8, y: 4)
|
||||
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 28, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
}
|
||||
|
||||
// Text Content
|
||||
@@ -24,11 +24,11 @@ struct HomeNavigationCard: View {
|
||||
Text(title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(subtitle)
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -36,10 +36,10 @@ struct HomeNavigationCard: View {
|
||||
// Chevron
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
}
|
||||
.padding(AppSpacing.lg)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ struct ImageThumbnailView: View {
|
||||
Button(action: onRemove) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.title3)
|
||||
.foregroundStyle(.white)
|
||||
.foregroundStyle(Color.appTextOnPrimary)
|
||||
.background {
|
||||
Circle()
|
||||
.fill(.black.opacity(0.6))
|
||||
|
||||
@@ -11,17 +11,17 @@ struct OverviewCard: View {
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: "chart.bar.fill")
|
||||
.font(.system(size: 20, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
}
|
||||
|
||||
Text("Overview")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
@@ -32,7 +32,7 @@ struct OverviewCard: View {
|
||||
icon: "house.fill",
|
||||
value: "\(summary.totalResidences)",
|
||||
label: "Properties",
|
||||
color: .blue
|
||||
color: Color.appPrimary
|
||||
)
|
||||
|
||||
Divider()
|
||||
@@ -42,7 +42,7 @@ struct OverviewCard: View {
|
||||
icon: "list.bullet",
|
||||
value: "\(summary.totalTasks)",
|
||||
label: "Total Tasks",
|
||||
color: .blue
|
||||
color: Color.appPrimary
|
||||
)
|
||||
|
||||
Divider()
|
||||
@@ -52,12 +52,12 @@ struct OverviewCard: View {
|
||||
icon: "clock.fill",
|
||||
value: "\(summary.totalPending)",
|
||||
label: "Pending",
|
||||
color: .orange
|
||||
color: Color.appAccent
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.xl)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.xl)
|
||||
.shadow(color: AppShadow.lg.color, radius: AppShadow.lg.radius, x: AppShadow.lg.x, y: AppShadow.lg.y)
|
||||
.padding(.horizontal, AppSpacing.md)
|
||||
|
||||
@@ -4,7 +4,7 @@ struct StatView: View {
|
||||
let icon: String
|
||||
let value: String
|
||||
let label: String
|
||||
var color: Color = .blue
|
||||
var color: Color = Color.appPrimary
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: AppSpacing.sm) {
|
||||
@@ -21,11 +21,11 @@ struct StatView: View {
|
||||
Text(value)
|
||||
.font(.title2.weight(.semibold))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(label)
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
@@ -5,15 +5,16 @@ struct EmptyResidencesView: View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "house")
|
||||
.font(.system(size: 80))
|
||||
.foregroundColor(.blue.opacity(0.6))
|
||||
.foregroundColor(Color.appPrimary.opacity(0.6))
|
||||
|
||||
Text("No properties yet")
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Add your first property to get started!")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ struct PropertyDetailItem: View {
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.font(.caption)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
Text(value)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(label)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,18 @@ struct PropertyHeaderCard: View {
|
||||
HStack {
|
||||
Image(systemName: "house.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(residence.name)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
if let propertyType = residence.propertyType {
|
||||
Text(propertyType)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,18 +33,19 @@ struct PropertyHeaderCard: View {
|
||||
if let streetAddress = residence.streetAddress {
|
||||
Label(streetAddress, systemImage: "mappin.circle.fill")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
|
||||
if residence.city != nil || residence.stateProvince != nil || residence.postalCode != nil {
|
||||
Text("\(residence.city ?? ""), \(residence.stateProvince ?? "") \(residence.postalCode ?? "")")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
if let country = residence.country, !country.isEmpty {
|
||||
Text(country)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +64,9 @@ struct PropertyHeaderCard: View {
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,26 +10,26 @@ struct ResidenceCard: View {
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: AppRadius.sm)
|
||||
.fill(LinearGradient(colors: [.blue, .blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
|
||||
.frame(width: 44, height: 44)
|
||||
.shadow(color: .blue.opacity(0.3), radius: 6, y: 3)
|
||||
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
|
||||
|
||||
Image(systemName: "house.fill")
|
||||
.font(.system(size: 20, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
||||
Text(residence.name)
|
||||
.font(.title3.weight(.semibold))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.lineLimit(1)
|
||||
|
||||
if let propertyType = residence.propertyType {
|
||||
Text(propertyType)
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.textCase(.uppercase)
|
||||
.tracking(0.5)
|
||||
}
|
||||
@@ -40,11 +40,11 @@ struct ResidenceCard: View {
|
||||
if residence.isPrimary {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(.purple.opacity(0.1))
|
||||
.fill(Color.appAccent.opacity(0.2))
|
||||
.frame(width: 32, height: 32)
|
||||
Image(systemName: "star.fill")
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.foregroundColor(.purple)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,10 +55,10 @@ struct ResidenceCard: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text(streetAddress)
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +66,10 @@ struct ResidenceCard: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
Image(systemName: "location.fill")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text("\(residence.city ?? ""), \(residence.stateProvince ?? "")")
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,13 +86,13 @@ struct ResidenceCard: View {
|
||||
icon: category.icons.ios,
|
||||
value: "\(category.count)",
|
||||
label: category.displayName,
|
||||
color: Color(hex: category.color) ?? .gray
|
||||
color: Color(hex: category.color) ?? Color.appTextSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
|
||||
}
|
||||
@@ -160,5 +160,5 @@ struct ResidenceCard: View {
|
||||
updatedAt: "2024-01-01T00:00:00Z"
|
||||
))
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.background(Color.appBackgroundPrimary)
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ struct ShareCodeCard: View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Share Code")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
if let shareCode = shareCode {
|
||||
Text(shareCode.code)
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
} else {
|
||||
Text("No active code")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ struct ShareCodeCard: View {
|
||||
if shareCode != nil {
|
||||
Text("Share this code with others to give them access to \(residenceName)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ struct SummaryCard: View {
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,16 @@ struct SummaryStatView: View {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.font(.title3)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
Text(value)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
@@ -19,7 +19,7 @@ struct TaskStatChip: View {
|
||||
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,16 @@ struct UserListItem: View {
|
||||
Text(user.username)
|
||||
.font(.body)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
if isOwner {
|
||||
Text("Owner")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.cornerRadius(4)
|
||||
}
|
||||
}
|
||||
@@ -31,7 +32,7 @@ struct UserListItem: View {
|
||||
if !user.email.isEmpty {
|
||||
Text(user.email)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
let fullName = [user.firstName, user.lastName]
|
||||
@@ -42,7 +43,7 @@ struct UserListItem: View {
|
||||
if !fullName.isEmpty {
|
||||
Text(fullName)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +52,12 @@ struct UserListItem: View {
|
||||
if isPrimaryOwner && !isOwner {
|
||||
Button(action: onRemove) {
|
||||
Image(systemName: "trash")
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ struct CompletionCardView: View {
|
||||
Text(formatDate(completion.completionDate))
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -23,10 +23,10 @@ struct CompletionCardView: View {
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Color.orange.opacity(0.1))
|
||||
.background(Color.appAccent.opacity(0.1))
|
||||
.cornerRadius(6)
|
||||
}
|
||||
}
|
||||
@@ -36,38 +36,38 @@ struct CompletionCardView: View {
|
||||
HStack(alignment: .top, spacing: 6) {
|
||||
Image(systemName: "wrench.and.screwdriver")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("By: \(contractorDetails.name)")
|
||||
.font(.caption2)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
if let company = contractorDetails.company {
|
||||
Text(company)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let completedBy = completion.completedByName {
|
||||
Text("By: \(completedBy)")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
if let cost = completion.actualCost {
|
||||
Text("Cost: $\(cost)")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
|
||||
if let notes = completion.notes {
|
||||
Text(notes)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
@@ -85,14 +85,14 @@ struct CompletionCardView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.foregroundColor(.blue)
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.background(Color(.systemGray6))
|
||||
.background(Color.appBackgroundSecondary.opacity(0.5))
|
||||
.cornerRadius(8)
|
||||
.sheet(isPresented: $showPhotoSheet) {
|
||||
if let images = completion.images {
|
||||
|
||||
@@ -38,21 +38,21 @@ struct DynamicTaskCard: View {
|
||||
if let description = task.description_, !description.isEmpty {
|
||||
Text(description)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label(task.frequency.displayName, systemImage: "repeat")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if let due_date = task.dueDate {
|
||||
Label(formatDate(due_date), systemImage: "calendar")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +62,15 @@ struct DynamicTaskCard: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
Text("Completions (\(task.completions.count))")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Spacer()
|
||||
Image(systemName: isCompletionsExpanded ? "chevron.up" : "chevron.down")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
@@ -102,12 +102,12 @@ struct DynamicTaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.foregroundColor(.blue)
|
||||
.background(Color.appPrimary.opacity(0.1))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.blue, lineWidth: 2)
|
||||
.stroke(Color.appPrimary, lineWidth: 2)
|
||||
)
|
||||
}
|
||||
.zIndex(10)
|
||||
@@ -115,7 +115,7 @@ struct DynamicTaskCard: View {
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(12)
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
|
||||
.simultaneousGesture(TapGesture(), including: .subviews)
|
||||
|
||||
@@ -18,7 +18,7 @@ struct DynamicTaskColumnView: View {
|
||||
}
|
||||
|
||||
private var columnColor: Color {
|
||||
Color(hex: column.color) ?? .primary
|
||||
Color(hex: column.color) ?? Color.appTextPrimary
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -40,7 +40,7 @@ struct DynamicTaskColumnView: View {
|
||||
Text("\(column.count)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(columnColor)
|
||||
@@ -55,7 +55,7 @@ struct DynamicTaskColumnView: View {
|
||||
|
||||
Text("No tasks")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 40)
|
||||
|
||||
@@ -5,15 +5,15 @@ struct EmptyTasksView: View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "checkmark.circle")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(.gray.opacity(0.5))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.5))
|
||||
|
||||
Text("No tasks yet")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(32)
|
||||
.background(Color(.systemBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ struct PhotoViewerSheet: View {
|
||||
VStack {
|
||||
Image(systemName: "photo")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.gray)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text("Failed to load image")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(height: 300)
|
||||
@unknown default:
|
||||
@@ -46,12 +46,14 @@ struct PhotoViewerSheet: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Caption")
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text(caption)
|
||||
.font(.body)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
@@ -104,10 +106,10 @@ struct PhotoViewerSheet: View {
|
||||
VStack {
|
||||
Image(systemName: "photo")
|
||||
.font(.system(size: 40))
|
||||
.foregroundColor(.gray)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
Text("Failed to load")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(height: 150)
|
||||
@unknown default:
|
||||
@@ -119,7 +121,7 @@ struct PhotoViewerSheet: View {
|
||||
if let caption = image.caption {
|
||||
Text(caption)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ struct PriorityBadge: View {
|
||||
|
||||
private var priorityColor: Color {
|
||||
switch priority.lowercased() {
|
||||
case "high": return .red
|
||||
case "medium": return .orange
|
||||
case "low": return .green
|
||||
default: return Color(.tertiaryLabel)
|
||||
case "high": return Color.appError
|
||||
case "medium": return Color.appAccent
|
||||
case "low": return Color.appPrimary
|
||||
default: return Color.appTextSecondary.opacity(0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,5 +36,5 @@ struct PriorityBadge: View {
|
||||
PriorityBadge(priority: "low")
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.background(Color.appBackgroundPrimary)
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ struct StatusBadge: View {
|
||||
|
||||
private var statusColor: Color {
|
||||
switch status {
|
||||
case "completed": return .green
|
||||
case "in_progress": return .orange
|
||||
case "pending": return .orange
|
||||
case "cancelled": return .red
|
||||
default: return Color(.tertiaryLabel)
|
||||
case "completed": return Color.appPrimary
|
||||
case "in_progress": return Color.appAccent
|
||||
case "pending": return Color.appAccent
|
||||
case "cancelled": return Color.appError
|
||||
default: return Color.appTextSecondary.opacity(0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,5 +41,5 @@ struct StatusBadge: View {
|
||||
StatusBadge(status: "cancelled")
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.background(Color.appBackgroundPrimary)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ struct CancelTaskButton: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.red)
|
||||
.tint(Color.appError)
|
||||
.alert("Cancel Task", isPresented: $showConfirmation) {
|
||||
Button("Cancel", role: .cancel) { }
|
||||
Button("Cancel Task", role: .destructive) {
|
||||
@@ -79,7 +79,7 @@ struct UncancelTaskButton: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.blue)
|
||||
.tint(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ struct MarkInProgressButton: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.orange)
|
||||
.tint(Color.appAccent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,6 @@ struct UnarchiveTaskButton: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.blue)
|
||||
.tint(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ struct TaskCard: View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xs) {
|
||||
Text(task.title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.lineLimit(2)
|
||||
|
||||
if let status = task.status {
|
||||
@@ -37,7 +37,7 @@ struct TaskCard: View {
|
||||
if let description = task.description_, !description.isEmpty {
|
||||
Text(description)
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.lineLimit(3)
|
||||
}
|
||||
|
||||
@@ -46,14 +46,14 @@ struct TaskCard: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
Image(systemName: "repeat")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
Text(task.frequency.displayName)
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.padding(.horizontal, AppSpacing.sm)
|
||||
.padding(.vertical, AppSpacing.xxs)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.xs)
|
||||
|
||||
Spacer()
|
||||
@@ -62,14 +62,14 @@ struct TaskCard: View {
|
||||
HStack(spacing: AppSpacing.xxs) {
|
||||
Image(systemName: "calendar")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundColor(Color(.tertiaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.7))
|
||||
Text(formatDate(dueDate))
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.padding(.horizontal, AppSpacing.sm)
|
||||
.padding(.vertical, AppSpacing.xxs)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.xs)
|
||||
}
|
||||
}
|
||||
@@ -83,20 +83,20 @@ struct TaskCard: View {
|
||||
HStack(spacing: AppSpacing.xs) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.green.opacity(0.1))
|
||||
.fill(Color.appAccent.opacity(0.1))
|
||||
.frame(width: 24, height: 24)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(Color.appAccent)
|
||||
}
|
||||
Text("Completions (\(task.completions.count))")
|
||||
.font(.footnote.weight(.medium))
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color(.label))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Spacer()
|
||||
Image(systemName: isCompletionsExpanded ? "chevron.up" : "chevron.down")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
@@ -127,8 +127,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 44)
|
||||
.foregroundColor(.orange)
|
||||
.background(Color.orange.opacity(0.1))
|
||||
.foregroundColor(Color.appAccent)
|
||||
.background(Color.appAccent.opacity(0.1))
|
||||
.cornerRadius(AppRadius.md)
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 44)
|
||||
.foregroundColor(.white)
|
||||
.background(.green)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.background(Color.appPrimary)
|
||||
.cornerRadius(AppRadius.md)
|
||||
}
|
||||
}
|
||||
@@ -164,8 +164,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 36)
|
||||
.foregroundColor(.blue)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.sm)
|
||||
}
|
||||
|
||||
@@ -179,8 +179,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 36)
|
||||
.foregroundColor(.red)
|
||||
.background(Color.red.opacity(0.1))
|
||||
.foregroundColor(Color.appError)
|
||||
.background(Color.appError.opacity(0.1))
|
||||
.cornerRadius(AppRadius.sm)
|
||||
}
|
||||
} else if let onUncancel = onUncancel {
|
||||
@@ -193,8 +193,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 36)
|
||||
.foregroundColor(.white)
|
||||
.background(.blue)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.background(Color.appPrimary)
|
||||
.cornerRadius(AppRadius.sm)
|
||||
}
|
||||
}
|
||||
@@ -211,8 +211,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 36)
|
||||
.foregroundColor(.blue)
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.sm)
|
||||
}
|
||||
}
|
||||
@@ -227,8 +227,8 @@ struct TaskCard: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 36)
|
||||
.foregroundColor(Color(.secondaryLabel))
|
||||
.background(Color(.tertiarySystemGroupedBackground))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.sm)
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ struct TaskCard: View {
|
||||
}
|
||||
}
|
||||
.padding(AppSpacing.md)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y)
|
||||
}
|
||||
@@ -287,5 +287,5 @@ struct TaskCard: View {
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.background(Color.appBackgroundPrimary)
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ struct TaskPill: View {
|
||||
|
||||
#Preview {
|
||||
HStack(spacing: 8) {
|
||||
TaskPill(count: 12, label: "Total", color: .blue)
|
||||
TaskPill(count: 5, label: "Pending", color: .orange)
|
||||
TaskPill(count: 3, label: "Done", color: .green)
|
||||
TaskPill(count: 12, label: "Total", color: Color.appPrimary)
|
||||
TaskPill(count: 5, label: "Pending", color: Color.appAccent)
|
||||
TaskPill(count: 3, label: "Done", color: Color.appPrimary)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ struct TasksSection: View {
|
||||
Text("Tasks")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
if hasNoTasks {
|
||||
EmptyTasksView()
|
||||
|
||||
@@ -99,7 +99,7 @@ struct AllTasksView: View {
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
if hasNoTasks && isLoadingTasks {
|
||||
@@ -113,18 +113,19 @@ struct AllTasksView: View {
|
||||
// Empty state with big button
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
|
||||
Image(systemName: "checklist")
|
||||
.font(.system(size: 64))
|
||||
.foregroundStyle(.blue.opacity(0.6))
|
||||
|
||||
.foregroundStyle(Color.appPrimary.opacity(0.6))
|
||||
|
||||
Text("No tasks yet")
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("Create your first task to get started")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Button(action: {
|
||||
@@ -147,7 +148,7 @@ struct AllTasksView: View {
|
||||
if residenceViewModel.myResidences?.residences.isEmpty ?? true {
|
||||
Text("Add a property first from the Residences tab")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -216,6 +217,8 @@ struct AllTasksView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("All Tasks")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -52,6 +52,7 @@ struct CompleteTaskView: View {
|
||||
} header: {
|
||||
Text("Task Details")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Contractor Selection Section
|
||||
Section {
|
||||
@@ -89,6 +90,7 @@ struct CompleteTaskView: View {
|
||||
} footer: {
|
||||
Text("Select a contractor if they completed this work, or leave blank for manual entry.")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Completion Details Section
|
||||
Section {
|
||||
@@ -117,6 +119,7 @@ struct CompleteTaskView: View {
|
||||
} footer: {
|
||||
Text("Add any additional details about completing this task.")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Notes Section
|
||||
Section {
|
||||
@@ -132,6 +135,7 @@ struct CompleteTaskView: View {
|
||||
} footer: {
|
||||
Text("Optional notes about the work completed.")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Rating Section
|
||||
Section {
|
||||
@@ -165,6 +169,7 @@ struct CompleteTaskView: View {
|
||||
} footer: {
|
||||
Text("Rate the quality of work from 1 to 5 stars.")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Images Section
|
||||
Section {
|
||||
@@ -175,7 +180,7 @@ struct CompleteTaskView: View {
|
||||
}) {
|
||||
Label("Take Photo", systemImage: "camera")
|
||||
.frame(maxWidth: .infinity)
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(Color.appPrimary)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
||||
@@ -187,7 +192,7 @@ struct CompleteTaskView: View {
|
||||
) {
|
||||
Label("Library", systemImage: "photo.on.rectangle.angled")
|
||||
.frame(maxWidth: .infinity)
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(Color.appPrimary)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
@@ -228,6 +233,7 @@ struct CompleteTaskView: View {
|
||||
} footer: {
|
||||
Text("Add up to 5 photos documenting the completed work.")
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
// Complete Button Section
|
||||
Section {
|
||||
@@ -243,11 +249,14 @@ struct CompleteTaskView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.listRowBackground(isSubmitting ? Color.gray : Color.green)
|
||||
.foregroundStyle(.white)
|
||||
.listRowBackground(isSubmitting ? Color.gray : Color.appPrimary)
|
||||
.foregroundStyle(Color.appTextOnPrimary)
|
||||
.disabled(isSubmitting)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Complete Task")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
@@ -397,7 +406,7 @@ struct ContractorPickerView: View {
|
||||
Spacer()
|
||||
if selectedContractor == nil {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +420,7 @@ struct ContractorPickerView: View {
|
||||
}
|
||||
} else if let errorMessage = contractorViewModel.errorMessage {
|
||||
Text(errorMessage)
|
||||
.foregroundStyle(.red)
|
||||
.foregroundStyle(Color.appError)
|
||||
.font(.caption)
|
||||
} else {
|
||||
ForEach(contractorViewModel.contractors, id: \.id) { contractor in
|
||||
@@ -445,7 +454,7 @@ struct ContractorPickerView: View {
|
||||
|
||||
if selectedContractor?.id == contractor.id {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(Color.appPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,15 +107,16 @@ struct TaskFormView: View {
|
||||
if !residenceError.isEmpty {
|
||||
Text(residenceError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
} header: {
|
||||
Text("Property")
|
||||
} footer: {
|
||||
Text("Required")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -125,7 +126,7 @@ struct TaskFormView: View {
|
||||
if !titleError.isEmpty {
|
||||
Text(titleError)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
|
||||
TextField("Description (optional)", text: $description, axis: .vertical)
|
||||
@@ -136,8 +137,9 @@ struct TaskFormView: View {
|
||||
} footer: {
|
||||
Text("Required: Title")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
Picker("Category", selection: $selectedCategory) {
|
||||
@@ -151,8 +153,9 @@ struct TaskFormView: View {
|
||||
} footer: {
|
||||
Text("Required")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
Picker("Frequency", selection: $selectedFrequency) {
|
||||
@@ -174,8 +177,9 @@ struct TaskFormView: View {
|
||||
} footer: {
|
||||
Text("Required: Frequency")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section {
|
||||
Picker("Priority", selection: $selectedPriority) {
|
||||
@@ -196,21 +200,24 @@ struct TaskFormView: View {
|
||||
} footer: {
|
||||
Text("Required: Both Priority and Status")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
Section(header: Text("Cost")) {
|
||||
TextField("Estimated Cost (optional)", text: $estimatedCost)
|
||||
.keyboardType(.decimalPad)
|
||||
.focused($focusedField, equals: .estimatedCost)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Section {
|
||||
Text(errorMessage)
|
||||
.foregroundColor(.red)
|
||||
.foregroundColor(Color.appError)
|
||||
.font(.caption)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
}
|
||||
}
|
||||
.disabled(isLoadingLookups)
|
||||
@@ -221,12 +228,15 @@ struct TaskFormView: View {
|
||||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
Text("Loading...")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(uiColor: .systemBackground).opacity(0.8))
|
||||
.background(Color.appBackgroundPrimary.opacity(0.8))
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle(isEditMode ? "Edit Task" : "Add Task")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -9,7 +9,7 @@ struct VerifyEmailView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
@@ -20,16 +20,17 @@ struct VerifyEmailView: View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "envelope.badge.shield.half.filled")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
.foregroundStyle(Color.appPrimary.gradient)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Text("Verify Your Email")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text("You must verify your email address to continue")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
@@ -38,12 +39,12 @@ struct VerifyEmailView: View {
|
||||
GroupBox {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "exclamationmark.shield.fill")
|
||||
.foregroundColor(.orange)
|
||||
.foregroundColor(Color.appAccent)
|
||||
.font(.title2)
|
||||
|
||||
Text("Email verification is required. Check your inbox for a 6-digit code.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
@@ -54,6 +55,7 @@ struct VerifyEmailView: View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Verification Code")
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.padding(.horizontal)
|
||||
|
||||
TextField("000000", text: $viewModel.code)
|
||||
@@ -75,7 +77,7 @@ struct VerifyEmailView: View {
|
||||
|
||||
Text("Code must be 6 digits")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
@@ -103,10 +105,10 @@ struct VerifyEmailView: View {
|
||||
.frame(height: 50)
|
||||
.background(
|
||||
viewModel.code.count == 6 && !viewModel.isLoading
|
||||
? Color.blue
|
||||
? Color.appPrimary
|
||||
: Color.gray.opacity(0.3)
|
||||
)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color.appTextOnPrimary)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.disabled(viewModel.code.count != 6 || viewModel.isLoading)
|
||||
@@ -117,12 +119,15 @@ struct VerifyEmailView: View {
|
||||
// Help Text
|
||||
Text("Didn't receive the code? Check your spam folder or contact support.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.interactiveDismissDisabled(true)
|
||||
|
||||
Reference in New Issue
Block a user