IceCubesApp/Packages/Account/Sources/Account/Edit/EditAccountView.swift
Thomas Ricouard 1f858414d8 format .
2024-02-14 12:48:14 +01:00

241 lines
7.2 KiB
Swift

import DesignSystem
import Env
import Models
import Network
import NukeUI
import SwiftUI
@MainActor
public struct EditAccountView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Client.self) private var client
@Environment(Theme.self) private var theme
@Environment(UserPreferences.self) private var userPrefs
@State private var viewModel = EditAccountViewModel()
public init() {}
public var body: some View {
NavigationStack {
Form {
if viewModel.isLoading {
loadingSection
} else {
imagesSection
aboutSection
fieldsSection
postSettingsSection
accountSection
}
}
.environment(\.editMode, .constant(.active))
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately)
#endif
.navigationTitle("account.edit.navigation-title")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
toolbarContent
}
.alert("account.edit.error.save.title",
isPresented: $viewModel.saveError,
actions: {
Button("alert.button.ok", action: {})
}, message: { Text("account.edit.error.save.message") })
.task {
viewModel.client = client
await viewModel.fetchAccount()
}
}
}
private var loadingSection: some View {
Section {
HStack {
Spacer()
ProgressView()
Spacer()
}
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
private var imagesSection: some View {
Section {
ZStack(alignment: .center) {
if let header = viewModel.header {
ZStack(alignment: .topLeading) {
LazyImage(url: header) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 150)
.clipShape(RoundedRectangle(cornerRadius: 8))
.clipped()
} else {
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(theme.primaryBackgroundColor)
.frame(height: 150)
}
}
.frame(height: 150)
}
}
if let avatar = viewModel.avatar {
ZStack(alignment: .bottomLeading) {
AvatarView(avatar, config: .account)
Menu {
Button("account.edit.avatar") {
viewModel.isChangingAvatar = true
viewModel.isPhotoPickerPresented = true
}
Button("account.edit.header") {
viewModel.isChangingHeader = true
viewModel.isPhotoPickerPresented = true
}
} label: {
Image(systemName: "photo.badge.plus")
.foregroundStyle(.white)
}
.buttonStyle(.borderedProminent)
.clipShape(Circle())
.offset(x: -8, y: 8)
}
}
}
.overlay {
if viewModel.isChangingAvatar || viewModel.isChangingHeader {
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color.black.opacity(0.40))
ProgressView()
}
}
}
.listRowInsets(EdgeInsets())
}
.listRowBackground(theme.secondaryBackgroundColor)
.photosPicker(isPresented: $viewModel.isPhotoPickerPresented,
selection: $viewModel.mediaPickers,
maxSelectionCount: 1,
matching: .any(of: [.images]),
photoLibrary: .shared())
}
@ViewBuilder
private var aboutSection: some View {
Section("account.edit.display-name") {
TextField("account.edit.display-name", text: $viewModel.displayName)
}
.listRowBackground(theme.primaryBackgroundColor)
Section("account.edit.about") {
TextField("account.edit.about", text: $viewModel.note, axis: .vertical)
.frame(maxHeight: 150)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
private var postSettingsSection: some View {
Section("account.edit.post-settings.section-title") {
if !userPrefs.useInstanceContentSettings {
Text("account.edit.post-settings.content-settings-reference")
}
Picker(selection: $viewModel.postPrivacy) {
ForEach(Models.Visibility.supportDefault, id: \.rawValue) { privacy in
Text(privacy.title).tag(privacy)
}
} label: {
Label("account.edit.post-settings.privacy", systemImage: "lock")
}
.pickerStyle(.menu)
Toggle(isOn: $viewModel.isSensitive) {
Label("account.edit.post-settings.sensitive", systemImage: "eye")
}
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
private var accountSection: some View {
Section("account.edit.account-settings.section-title") {
Toggle(isOn: $viewModel.isLocked) {
Label("account.edit.account-settings.private", systemImage: "lock")
}
Toggle(isOn: $viewModel.isBot) {
Label("account.edit.account-settings.bot", systemImage: "laptopcomputer.trianglebadge.exclamationmark")
}
Toggle(isOn: $viewModel.isDiscoverable) {
Label("account.edit.account-settings.discoverable", systemImage: "magnifyingglass")
}
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
private var fieldsSection: some View {
Section("account.edit.metadata-section-title") {
ForEach($viewModel.fields) { $field in
VStack(alignment: .leading) {
TextField("account.edit.metadata-name-placeholder", text: $field.name)
.font(.scaledHeadline)
TextField("account.edit.metadata-value-placeholder", text: $field.value)
.emojiText.size(Font.scaledBodyFont.emojiSize)
.emojiText.baselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
.foregroundColor(theme.tintColor)
}
}
.onMove(perform: { indexSet, newOffset in
viewModel.fields.move(fromOffsets: indexSet, toOffset: newOffset)
})
.onDelete { indexes in
if let index = indexes.first {
viewModel.fields.remove(at: index)
}
}
if viewModel.fields.count < 4 {
Button {
withAnimation {
viewModel.fields.append(.init(name: "", value: ""))
}
} label: {
Text("account.edit.add-metadata-button")
.foregroundColor(theme.tintColor)
}
}
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
CancelToolbarItem()
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task {
await viewModel.save()
dismiss()
}
} label: {
if viewModel.isSaving {
ProgressView()
} else {
Text("action.save").bold()
}
}
}
}
}