import SwiftUI struct ProfileView: View { let user: User let isCurrentUser: Bool @EnvironmentObject var appState: AppState @StateObject private var viewModel: ProfileViewModel @State private var showSettings = false init(user: User, isCurrentUser: Bool) { self.user = user self.isCurrentUser = isCurrentUser _viewModel = StateObject(wrappedValue: ProfileViewModel(userIdString: user.id)) } var body: some View { NavigationStack { ZStack { Color.nightBase.ignoresSafeArea() ScrollView { VStack(spacing: 0) { ProfileHeader( user: user, streak: viewModel.streak, isCurrentUser: isCurrentUser ) Divider().background(Color.nightBorder) // Posts if viewModel.isLoading { ProgressView().tint(.nightPurple).padding(40) } else if viewModel.posts.isEmpty { EmptyProfilePosts() } else { LazyVStack(spacing: 0) { ForEach(viewModel.posts) { post in PostRowView(post: post) {} Divider().background(Color.nightBorder).padding(.leading, 16) } } } Color.clear.frame(height: 100) } } } .navigationBarTitleDisplayMode(.inline) .toolbar { if isCurrentUser { ToolbarItem(placement: .navigationBarTrailing) { Button { showSettings = true } label: { Image(systemName: "gearshape") .foregroundColor(.nightSecondary) } } } } .sheet(isPresented: $showSettings) { SettingsView().environmentObject(appState) } } .task { await viewModel.load() } } } // MARK: - Profile Header struct ProfileHeader: View { let user: User let streak: Int let isCurrentUser: Bool @State private var isFollowing: Bool @State private var isFollowLoading = false init(user: User, streak: Int, isCurrentUser: Bool) { self.user = user self.streak = streak self.isCurrentUser = isCurrentUser _isFollowing = State(initialValue: user.isFollowing) } var body: some View { VStack(spacing: 0) { VStack(spacing: 16) { AvatarView(user: user, size: 76) VStack(spacing: 4) { Text(user.displayName) .font(.nightTitle(22)) .foregroundColor(.nightPrimary) Text("@\(user.username)") .font(.nightLabel(14)) .foregroundColor(.nightSecondary) if let bio = user.bio { Text(bio) .font(.nightBody(14)) .foregroundColor(.nightPrimary.opacity(0.75)) .multilineTextAlignment(.center) .padding(.top, 4) } } // Stats HStack(spacing: 36) { ProfileStat(value: user.postCount, label: "nächte") ProfileStat(value: user.followerCount, label: "follower") ProfileStat(value: user.followingCount, label: "following") } // Streak if streak > 0 { HStack(spacing: 6) { Image(systemName: streak >= 7 ? "flame.fill" : "flame") .foregroundColor(streak >= 7 ? .orange : .nightSecondary) Text("\(streak) Nächte in Folge") .font(.nightLabel(13, weight: streak >= 7 ? .semibold : .regular)) .foregroundColor(streak >= 7 ? .nightPrimary : .nightSecondary) } .padding(.horizontal, 14) .padding(.vertical, 7) .background(Color.nightRaised) .clipShape(Capsule()) } // Action button if isCurrentUser { Button("profil bearbeiten") {} .font(.nightLabel(14, weight: .medium)) .foregroundColor(.nightPrimary) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(Color.nightRaised) .clipShape(RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder(Color.nightBorder, lineWidth: 1) ) .padding(.horizontal, 48) } else { Button { Task { await toggleFollow() } } label: { Group { if isFollowLoading { ProgressView().tint(isFollowing ? .nightPrimary : .nightBase) } else { Text(isFollowing ? "entfolgen" : "folgen") .font(.nightLabel(14, weight: .semibold)) .foregroundColor(isFollowing ? .nightPrimary : .nightBase) } } .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(isFollowing ? Color.nightRaised : Color.nightPrimary) .clipShape(RoundedRectangle(cornerRadius: 10)) } .disabled(isFollowLoading) .padding(.horizontal, 48) } } .padding(.horizontal, 20) .padding(.top, 28) .padding(.bottom, 20) } } func toggleFollow() async { isFollowLoading = true defer { isFollowLoading = false } guard let uid = UUID(uuidString: user.id) else { return } do { if isFollowing { try await supabase.unfollow(userId: uid) } else { try await supabase.follow(userId: uid) } isFollowing.toggle() } catch { /* handle error */ } } } struct ProfileStat: View { let value: Int let label: String var body: some View { VStack(spacing: 3) { Text("\(value)") .font(.nightTitle(18)) .foregroundColor(.nightPrimary) Text(label) .font(.nightLabel(12)) .foregroundColor(.nightSecondary) } } } struct EmptyProfilePosts: View { var body: some View { VStack(spacing: 14) { Image(systemName: "moon.zzz") .font(.system(size: 36)) .foregroundColor(.nightTertiary) Text("noch keine nächte") .font(.nightLabel(15)) .foregroundColor(.nightSecondary) } .padding(.top, 60) } }