Apply Warm Organic design system to all iOS views

- Full-screen views: Added WarmGradientBackground() to CompleteTaskView,
  ContractorDetailView, DocumentDetailView, DocumentFormView,
  FeatureComparisonView, TaskTemplatesBrowserView, ManageUsersView,
  ContractorPickerView

- Onboarding: Redesigned all 8 screens with organic styling including
  animated hero sections, gradient buttons, decorative blobs

- Components: Updated ErrorView, EmptyStateView, EmptyResidencesView,
  EmptyTasksView, TaskSuggestionsView, StatView, SummaryStatView,
  CompletionCardView, DynamicTaskColumnView with organic styling

- Applied consistent patterns: OrganicSpacing, naturalShadow modifier,
  RoundedRectangle with .continuous style, rounded font designs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-17 09:05:47 -06:00
parent c3a9494b0f
commit b05e52521f
37 changed files with 5009 additions and 2759 deletions

View File

@@ -7,70 +7,170 @@ struct JoinResidenceView: View {
let onJoined: () -> Void
@State private var shareCode: String = ""
@FocusState private var isCodeFocused: Bool
var body: some View {
NavigationView {
Form {
Section {
TextField(L10n.Residences.shareCode, text: $shareCode)
.textInputAutocapitalization(.characters)
.autocorrectionDisabled()
.onChange(of: shareCode) { newValue in
// Limit to 6 characters and uppercase
if newValue.count > 6 {
shareCode = String(newValue.prefix(6))
}
shareCode = shareCode.uppercased()
viewModel.clearError()
}
.disabled(viewModel.isLoading)
} header: {
Text(L10n.Residences.enterShareCode)
} footer: {
Text(L10n.Residences.shareCodeFooter)
.foregroundColor(Color.appTextSecondary)
}
.listRowBackground(Color.appBackgroundSecondary)
ZStack {
WarmGradientBackground()
if let error = viewModel.errorMessage {
Section {
Text(error)
.foregroundColor(Color.appError)
}
.listRowBackground(Color.appBackgroundSecondary)
}
ScrollView(showsIndicators: false) {
VStack(spacing: OrganicSpacing.spacious) {
Spacer()
.frame(height: OrganicSpacing.comfortable)
Section {
Button(action: joinResidence) {
HStack {
Spacer()
if viewModel.isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
} else {
Text(L10n.Residences.joinButton)
.fontWeight(.semibold)
// Hero Section
VStack(spacing: OrganicSpacing.comfortable) {
ZStack {
Circle()
.fill(
RadialGradient(
colors: [
Color.appPrimary.opacity(0.15),
Color.appPrimary.opacity(0.05),
Color.clear
],
center: .center,
startRadius: 0,
endRadius: 60
)
)
.frame(width: 120, height: 120)
Image(systemName: "person.badge.plus")
.font(.system(size: 48, weight: .medium))
.foregroundColor(Color.appPrimary)
}
VStack(spacing: 8) {
Text(L10n.Residences.joinTitle)
.font(.system(size: 26, weight: .bold, design: .rounded))
.foregroundColor(Color.appTextPrimary)
Text(L10n.Residences.enterShareCode)
.font(.system(size: 15, weight: .medium))
.foregroundColor(Color.appTextSecondary)
.multilineTextAlignment(.center)
}
Spacer()
}
// Form Card
VStack(spacing: 20) {
// Share Code Input
VStack(alignment: .leading, spacing: 8) {
Text(L10n.Residences.shareCode.uppercased())
.font(.system(size: 11, weight: .semibold, design: .rounded))
.foregroundColor(Color.appTextSecondary)
.tracking(1.2)
TextField("ABC123", text: $shareCode)
.font(.system(size: 32, weight: .bold, design: .rounded))
.multilineTextAlignment(.center)
.textInputAutocapitalization(.characters)
.autocorrectionDisabled()
.focused($isCodeFocused)
.disabled(viewModel.isLoading)
.padding(20)
.background(Color.appBackgroundPrimary.opacity(0.5))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(isCodeFocused ? Color.appPrimary : Color.appTextSecondary.opacity(0.15), lineWidth: 1.5)
)
.onChange(of: shareCode) { newValue in
if newValue.count > 6 {
shareCode = String(newValue.prefix(6))
}
shareCode = shareCode.uppercased()
viewModel.clearError()
}
Text(L10n.Residences.shareCodeFooter)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.appTextSecondary)
}
// Error Message
if let error = viewModel.errorMessage {
HStack(spacing: 10) {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color.appError)
Text(error)
.font(.system(size: 14, weight: .medium))
.foregroundColor(Color.appError)
Spacer()
}
.padding(16)
.background(Color.appError.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
}
// Join Button
Button(action: joinResidence) {
HStack(spacing: 8) {
if viewModel.isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
} else {
Image(systemName: "person.badge.plus")
}
Text(viewModel.isLoading ? "Joining..." : L10n.Residences.joinButton)
.font(.headline)
.fontWeight(.semibold)
}
.frame(maxWidth: .infinity)
.frame(height: 56)
.foregroundColor(Color.appTextOnPrimary)
.background(
shareCode.count == 6 && !viewModel.isLoading
? AnyShapeStyle(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
: AnyShapeStyle(Color.appTextSecondary)
)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.shadow(
color: shareCode.count == 6 && !viewModel.isLoading ? Color.appPrimary.opacity(0.3) : .clear,
radius: 10,
y: 5
)
}
.disabled(shareCode.count != 6 || viewModel.isLoading)
// Cancel Button
Button(action: { dismiss() }) {
Text(L10n.Common.cancel)
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(Color.appTextSecondary)
}
.disabled(viewModel.isLoading)
.padding(.top, 8)
}
.padding(OrganicSpacing.cozy)
.background(OrganicJoinCardBackground())
.clipShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
.naturalShadow(.pronounced)
.padding(.horizontal, 16)
Spacer()
}
.disabled(shareCode.count != 6 || viewModel.isLoading)
}
.listRowBackground(Color.appBackgroundSecondary)
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.appBackgroundPrimary)
.navigationTitle(L10n.Residences.joinTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(L10n.Common.cancel) {
dismiss()
Button(action: { dismiss() }) {
Image(systemName: "xmark")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(Color.appTextSecondary)
.padding(8)
.background(Color.appBackgroundSecondary.opacity(0.8))
.clipShape(Circle())
}
.disabled(viewModel.isLoading)
}
}
.onAppear {
isCodeFocused = true
}
}
}
@@ -85,7 +185,38 @@ struct JoinResidenceView: View {
onJoined()
dismiss()
}
// Error is handled by ViewModel and displayed via viewModel.errorMessage
}
}
}
// MARK: - Background
private struct OrganicJoinCardBackground: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
Color.appBackgroundSecondary
GeometryReader { geo in
OrganicBlobShape(variation: 1)
.fill(
RadialGradient(
colors: [
Color.appPrimary.opacity(colorScheme == .dark ? 0.08 : 0.05),
Color.appPrimary.opacity(0.01)
],
center: .center,
startRadius: 0,
endRadius: geo.size.width * 0.5
)
)
.frame(width: geo.size.width * 0.6, height: geo.size.height * 0.5)
.offset(x: geo.size.width * 0.4, y: geo.size.height * 0.4)
.blur(radius: 20)
}
GrainTexture(opacity: 0.015)
}
}
}

View File

@@ -21,8 +21,7 @@ struct ManageUsersView: View {
var body: some View {
NavigationView {
ZStack {
Color.appBackgroundPrimary
.ignoresSafeArea()
WarmGradientBackground()
if isLoading {
ProgressView()
@@ -71,7 +70,6 @@ struct ManageUsersView: View {
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.appBackgroundPrimary)
.navigationTitle(L10n.Residences.manageUsers)
.navigationBarTitleDisplayMode(.inline)
.toolbar {