payfrit-works-ios/PayfritWorks/Views/ProfileScreen.swift
John Pinkyfloyd c71b9f7dea Add ios-marketing idiom for App Store icon display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-10 19:37:59 -08:00

153 lines
5.4 KiB
Swift

import SwiftUI
struct ProfileScreen: View {
@EnvironmentObject var appState: AppState
@Environment(\.dismiss) private var dismiss
@State private var appearAnimation = false
@State private var avatarURLLoaded: URL?
private var displayName: String {
appState.userName ?? "Worker"
}
private var avatarURL: URL? {
if let loaded = avatarURLLoaded { return loaded }
guard let urlString = appState.userPhotoUrl, !urlString.isEmpty else { return nil }
return URL(string: urlString)
}
var body: some View {
ScrollView {
VStack(spacing: 20) {
// Avatar section
VStack(spacing: 8) {
if let url = avatarURL {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure:
placeholderAvatar
default:
ProgressView()
.frame(width: 80, height: 80)
}
}
.frame(width: 80, height: 80)
.clipShape(Circle())
.overlay(Circle().stroke(Color.payfritGreen.opacity(0.3), lineWidth: 2))
.shadow(color: .black.opacity(0.1), radius: 6, y: 3)
} else {
placeholderAvatar
}
Text(displayName)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.primary)
Text("Worker")
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.top, 16)
.scaleEffect(appearAnimation ? 1 : 0.9)
.opacity(appearAnimation ? 1 : 0)
// Info section
VStack(alignment: .leading, spacing: 12) {
Text("Account Information")
.font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.payfritGreen)
.padding(.horizontal, 16)
VStack(spacing: 0) {
infoRow(icon: "person.fill", label: "Name", value: displayName)
Divider().padding(.leading, 54)
infoRow(icon: "briefcase.fill", label: "Role", value: "Worker")
}
.background(Color(.secondarySystemGroupedBackground))
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.padding(.horizontal, 16)
}
.opacity(appearAnimation ? 1 : 0)
.offset(y: appearAnimation ? 0 : 15)
// Note
Text("Profile information is managed through your employer. Contact your manager if you need to update your details.")
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
.padding(.top, 8)
.opacity(appearAnimation ? 1 : 0)
Spacer()
.frame(height: 20)
}
}
.background(Color(.systemGroupedBackground).ignoresSafeArea())
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
.task {
do {
if let urlString = try await APIService.shared.getAvatarUrl(),
let url = URL(string: urlString) {
avatarURLLoaded = url
}
} catch {
// Avatar load failed
}
}
.onAppear {
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
appearAnimation = true
}
}
}
private var placeholderAvatar: some View {
Image(systemName: "person.circle.fill")
.resizable()
.foregroundStyle(.linearGradient(
colors: [Color.payfritGreen.opacity(0.6), Color.payfritGreen.opacity(0.3)],
startPoint: .top,
endPoint: .bottom
))
.frame(width: 80, height: 80)
.shadow(color: .black.opacity(0.1), radius: 6, y: 3)
}
private func infoRow(icon: String, label: String, value: String) -> some View {
HStack(spacing: 14) {
Image(systemName: icon)
.font(.subheadline)
.foregroundColor(.payfritGreen)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(label)
.font(.caption)
.foregroundColor(.secondary)
Text(value)
.font(.subheadline)
.foregroundColor(.primary)
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
}
#Preview {
NavigationStack {
ProfileScreen()
.environmentObject(AppState())
}
}