Add shared utilities and refactor iOS codebase for DRY compliance
Create centralized shared utilities in iosApp/Shared/: - Extensions: ViewExtensions, DateExtensions, StringExtensions, DoubleExtensions - Components: FormComponents, SharedEmptyStateView, ButtonStyles - Modifiers: CardModifiers - Utilities: ValidationHelpers, ErrorMessages Migrate existing views to use shared utilities: - LoginView: Use IconTextField, FieldLabel, FieldError, OrganicPrimaryButton - TaskFormView: Use .loadingOverlay() modifier - TaskCard/DynamicTaskCard: Use .toFormattedDate() extension - CompletionCardView: Use .toCurrency() (with KotlinDouble support) - ResidenceDetailView: Use OrganicEmptyState, StandardLoadingView - Profile views: Use .standardFormStyle(), .sectionBackground() - Form views: Use consistent form styling modifiers Benefits: - Eliminates ~180 lines of duplicate code - Consistent styling across all forms and components - KotlinDouble extensions for seamless KMM interop - Single source of truth for UI patterns 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,12 +197,7 @@ private extension ResidenceDetailView {
|
||||
}
|
||||
|
||||
var loadingView: some View {
|
||||
VStack(spacing: 16) {
|
||||
ProgressView()
|
||||
Text(L10n.Residences.loadingResidence)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
StandardLoadingView(message: L10n.Residences.loadingResidence)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -282,43 +277,12 @@ private extension ResidenceDetailView {
|
||||
.padding()
|
||||
} else if contractors.isEmpty {
|
||||
// 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(OrganicSpacing.spacious)
|
||||
.background(OrganicCardBackground(showBlob: true, blobVariation: 1))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
.naturalShadow(.subtle)
|
||||
OrganicEmptyState(
|
||||
icon: "person.crop.circle.badge.plus",
|
||||
title: L10n.Residences.noContractors,
|
||||
subtitle: L10n.Residences.addContractorsPrompt,
|
||||
blobVariation: 1
|
||||
)
|
||||
} else {
|
||||
// Contractors list
|
||||
VStack(spacing: 12) {
|
||||
|
||||
Reference in New Issue
Block a user