import SwiftUI struct LoadingView: View { var message: String = "Loading..." var body: some View { VStack(spacing: 16) { ProgressView() .scaleEffect(1.5) Text(message) .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.systemBackground)) } } struct AppLaunchLoadingView: View { var messageKey: LocalizedStringKey = "loading" var body: some View { VStack(spacing: 20) { Spacer() Image("BrandMark") .resizable() .scaledToFit() .frame(width: 140, height: 140) .padding(16) .background(Color.appPrimary.opacity(0.08)) .cornerRadius(28) ProgressView() .scaleEffect(1.2) Text(messageKey) .font(.subheadline) .foregroundColor(.secondary) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(AppBackground()) } } // MARK: - Skeleton Loading struct SkeletonView: View { @State private var isAnimating = false var body: some View { Rectangle() .fill( LinearGradient( colors: [ Color.gray.opacity(0.1), Color.gray.opacity(0.2), Color.gray.opacity(0.1) ], startPoint: .leading, endPoint: .trailing ) ) .offset(x: isAnimating ? 200 : -200) .animation( Animation.linear(duration: 1.5) .repeatForever(autoreverses: false), value: isAnimating ) .mask(Rectangle()) .onAppear { isAnimating = true } } } struct SkeletonCardView: View { var body: some View { VStack(alignment: .leading, spacing: 12) { SkeletonView() .frame(width: 100, height: 16) .cornerRadius(4) SkeletonView() .frame(height: 32) .cornerRadius(4) HStack { SkeletonView() .frame(width: 80, height: 14) .cornerRadius(4) Spacer() SkeletonView() .frame(width: 60, height: 14) .cornerRadius(4) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) } } // MARK: - Empty State View struct EmptyStateView: View { let icon: String let title: String let message: String var actionTitle: String? var action: (() -> Void)? var body: some View { VStack(spacing: 20) { Image(systemName: icon) .font(.system(size: 60)) .foregroundColor(.secondary) Text(title) .font(.title2.weight(.semibold)) Text(message) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 40) if let actionTitle = actionTitle, let action = action { Button(action: action) { Text(actionTitle) .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: 200) .background(Color.appPrimary) .cornerRadius(AppConstants.UI.cornerRadius) } } } .frame(maxWidth: .infinity, maxHeight: .infinity) } } // MARK: - Error View struct ErrorView: View { let message: String var retryAction: (() -> Void)? var body: some View { VStack(spacing: 16) { Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 48)) .foregroundColor(.appWarning) Text("Something went wrong") .font(.headline) Text(message) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 40) if let retryAction = retryAction { Button(action: retryAction) { Label("Try Again", systemImage: "arrow.clockwise") .font(.subheadline.weight(.semibold)) .padding(.horizontal, 20) .padding(.vertical, 10) .background(Color.appPrimary) .foregroundColor(.white) .cornerRadius(20) } } } .frame(maxWidth: .infinity, maxHeight: .infinity) } } // MARK: - Success View struct SuccessView: View { let title: String let message: String var action: (() -> Void)? var body: some View { VStack(spacing: 20) { ZStack { Circle() .fill(Color.positiveGreen.opacity(0.1)) .frame(width: 100, height: 100) Image(systemName: "checkmark.circle.fill") .font(.system(size: 60)) .foregroundColor(.positiveGreen) } Text(title) .font(.title2.weight(.bold)) Text(message) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 40) if let action = action { Button(action: action) { Text("Continue") .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: 200) .background(Color.appPrimary) .cornerRadius(AppConstants.UI.cornerRadius) } } } } } // MARK: - Toast View struct ToastView: View { enum ToastType { case success, error, info var icon: String { switch self { case .success: return "checkmark.circle.fill" case .error: return "xmark.circle.fill" case .info: return "info.circle.fill" } } var color: Color { switch self { case .success: return .positiveGreen case .error: return .negativeRed case .info: return .appPrimary } } } let message: String let type: ToastType var body: some View { HStack(spacing: 12) { Image(systemName: type.icon) .foregroundColor(type.color) Text(message) .font(.subheadline) } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.1), radius: 10, y: 5) } } #Preview { VStack(spacing: 20) { LoadingView() .frame(height: 100) SkeletonCardView() EmptyStateView( icon: "tray", title: "No Data", message: "Start adding your investments to see them here.", actionTitle: "Get Started", action: {} ) .frame(height: 300) ToastView(message: "Successfully saved!", type: .success) } .padding() }