Add Warm Organic design system to iOS app
- Add OrganicDesign.swift with reusable components: - WarmGradientBackground, OrganicBlobShape, GrainTexture - OrganicDivider, OrganicCardBackground, NaturalShadow modifier - OrganicSpacing constants (cozy, comfortable, spacious, airy) - Update high-priority screens with organic styling: - LoginView: hero glow, organic card background, rounded fonts - ResidenceDetailView, ResidencesListView: warm backgrounds - ResidenceCard, SummaryCard, PropertyHeaderCard: organic cards - TaskCard: metadata pills, secondary buttons, card background - TaskFormView: organic loading overlay, templates button - CompletionHistorySheet: organic loading/error/empty states - ProfileView, NotificationPreferencesView, ThemeSelectionView - Update task badges with icons and capsule styling: - PriorityBadge: priority-specific icons - StatusBadge: status-specific icons - Fix TaskCard isOverdue error using DateUtils.isOverdue() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,8 +47,7 @@ struct ResidenceDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.appBackgroundPrimary
|
||||
.ignoresSafeArea()
|
||||
WarmGradientBackground()
|
||||
|
||||
mainContent
|
||||
}
|
||||
@@ -208,19 +207,19 @@ private extension ResidenceDetailView {
|
||||
|
||||
@ViewBuilder
|
||||
func contentView(for residence: ResidenceResponse) -> some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: OrganicSpacing.comfortable) {
|
||||
PropertyHeaderCard(residence: residence)
|
||||
.padding(.horizontal)
|
||||
.padding(.top)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 8)
|
||||
|
||||
tasksSection
|
||||
.padding(.horizontal)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
contractorsSection
|
||||
.padding(.horizontal)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.padding(.bottom)
|
||||
.padding(.bottom, OrganicSpacing.airy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,49 +247,81 @@ private extension ResidenceDetailView {
|
||||
|
||||
@ViewBuilder
|
||||
var contractorsSection: some View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.md) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
// Section Header
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.appPrimary.opacity(0.12))
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
|
||||
Text(L10n.Residences.contractors)
|
||||
.font(.title2.weight(.bold))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.font(.system(size: 20, weight: .bold, design: .rounded))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, AppSpacing.sm)
|
||||
.padding(.top, 8)
|
||||
|
||||
if isLoadingContractors {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.tint(Color.appPrimary)
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.padding(OrganicSpacing.cozy)
|
||||
} else if let error = contractorsError {
|
||||
Text("\(L10n.Common.error): \(error)")
|
||||
.foregroundColor(Color.appError)
|
||||
.padding()
|
||||
} else if contractors.isEmpty {
|
||||
// Empty state
|
||||
VStack(spacing: AppSpacing.md) {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.6))
|
||||
Text(L10n.Residences.noContractors)
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text(L10n.Residences.addContractorsPrompt)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
// Empty state with organic styling
|
||||
VStack(spacing: 16) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
colors: [
|
||||
Color.appPrimary.opacity(0.12),
|
||||
Color.appPrimary.opacity(0.04)
|
||||
],
|
||||
center: .center,
|
||||
startRadius: 0,
|
||||
endRadius: 50
|
||||
)
|
||||
)
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.font(.system(size: 32, weight: .medium))
|
||||
.foregroundColor(Color.appPrimary.opacity(0.6))
|
||||
}
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.Residences.noContractors)
|
||||
.font(.system(size: 17, weight: .semibold, design: .rounded))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
|
||||
Text(L10n.Residences.addContractorsPrompt)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(AppSpacing.xl)
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
.padding(OrganicSpacing.spacious)
|
||||
.background(OrganicCardBackground(showBlob: true, blobVariation: 1))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
.naturalShadow(.subtle)
|
||||
} else {
|
||||
// Contractors list
|
||||
VStack(spacing: AppSpacing.sm) {
|
||||
VStack(spacing: 12) {
|
||||
ForEach(contractors, id: \.id) { contractor in
|
||||
NavigationLink(destination: ContractorDetailView(contractorId: contractor.id)) {
|
||||
ContractorCard(
|
||||
@@ -300,7 +331,7 @@ private extension ResidenceDetailView {
|
||||
}
|
||||
)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.buttonStyle(OrganicCardButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +339,17 @@ private extension ResidenceDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Organic Card Button Style
|
||||
|
||||
private struct OrganicCardButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
|
||||
.opacity(configuration.isPressed ? 0.9 : 1.0)
|
||||
.animation(.easeOut(duration: 0.15), value: configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toolbars
|
||||
|
||||
private extension ResidenceDetailView {
|
||||
|
||||
Reference in New Issue
Block a user