diff --git a/Application/Presentation/Development/Sources/Record/Detail/RecordDetailView.swift b/Application/Presentation/Development/Sources/Record/Detail/RecordDetailView.swift index 21052d09..995c1345 100644 --- a/Application/Presentation/Development/Sources/Record/Detail/RecordDetailView.swift +++ b/Application/Presentation/Development/Sources/Record/Detail/RecordDetailView.swift @@ -95,7 +95,7 @@ public struct RecordDetailView: View { private var topBar: some View { HStack { - RecordBackButton(action: dismiss.callAsFunction) + NavigationBackButton(action: dismiss.callAsFunction) .disabled(store.isRestoring) Spacer() } diff --git a/Application/Presentation/Development/Sources/Record/History/RecordVersionDetailView.swift b/Application/Presentation/Development/Sources/Record/History/RecordVersionDetailView.swift index 9f76507b..494592a6 100644 --- a/Application/Presentation/Development/Sources/Record/History/RecordVersionDetailView.swift +++ b/Application/Presentation/Development/Sources/Record/History/RecordVersionDetailView.swift @@ -51,7 +51,7 @@ struct RecordVersionDetailView: View { private var topBar: some View { HStack { - RecordBackButton(action: dismiss.callAsFunction) + NavigationBackButton(action: dismiss.callAsFunction) .disabled(store.isRestoring) Spacer() } diff --git a/Application/Presentation/Development/Sources/Record/History/RecordVersionHistoryView.swift b/Application/Presentation/Development/Sources/Record/History/RecordVersionHistoryView.swift index e6dde50e..fdac4f37 100644 --- a/Application/Presentation/Development/Sources/Record/History/RecordVersionHistoryView.swift +++ b/Application/Presentation/Development/Sources/Record/History/RecordVersionHistoryView.swift @@ -47,7 +47,7 @@ struct RecordVersionHistoryView: View { private var topBar: some View { HStack { - RecordBackButton(action: dismiss.callAsFunction) + NavigationBackButton(action: dismiss.callAsFunction) Spacer() } .padding(.horizontal) @@ -264,26 +264,6 @@ struct RecordVersionBadge: View { } } -struct RecordBackButton: View { - let action: () -> Void - - var body: some View { - if #available(iOS 26.0, *) { - Button(action: action) { - Image(systemName: "chevron.left") - } - .topBarButtonStyle() - } else { - Button(action: action) { - Image(systemName: "chevron.left") - .font(.title3.weight(.semibold)) - .frame(width: 28, height: 28) - } - .adaptiveButtonStyle(shape: .circle, color: .surface, glassEffect: .enabled) - } - } -} - private struct VersionDestination: Identifiable { let version: DevelopmentRecord.Version var id: String { version.id } diff --git a/Application/Presentation/HomeTab/Sources/Category/CheckBox.swift b/Application/Presentation/HomeTab/Sources/Category/CheckBox.swift index 8eeec0d2..8e0c694f 100644 --- a/Application/Presentation/HomeTab/Sources/Category/CheckBox.swift +++ b/Application/Presentation/HomeTab/Sources/Category/CheckBox.swift @@ -30,7 +30,3 @@ struct CheckBox: View { .font(font) } } - -#Preview { - CheckBox(isChecked: true) -} diff --git a/Application/Presentation/HomeTab/Sources/Home/HomeView.swift b/Application/Presentation/HomeTab/Sources/Home/HomeView.swift index f4e7f6c1..939822a1 100644 --- a/Application/Presentation/HomeTab/Sources/Home/HomeView.swift +++ b/Application/Presentation/HomeTab/Sources/Home/HomeView.swift @@ -12,7 +12,6 @@ import Domain import PresentationShared public struct HomeView: View { - @Environment(\.colorScheme) private var colorScheme @Environment(\.dynamicTypeSize) private var dynamicTypeSize @Environment(\.openWindow) private var openWindow @Environment(\.isiOSAppOnMac) private var isiOSAppOnMac @@ -341,14 +340,14 @@ public struct HomeView: View { private func todoCategoryRow(_ item: TodoCategoryItem) -> some View { NavigationLink(value: HomeRoute.category(item)) { VStack(spacing: 10) { - RoundedRectangle(cornerRadius: 20) - .fill(categoryIconBackground(item)) + Image(systemName: item.symbolName) + .font(.title2.bold()) .frame(width: categoryIconSize, height: categoryIconSize) - .overlay { - Image(systemName: item.symbolName) - .font(.title2.bold()) - .foregroundStyle(categoryIconForeground(item)) - } + .iconStyle( + color: item.color, + in: RoundedRectangle(cornerRadius: 20) + ) + .accessibilityHidden(true) Text(item.localizedName) .font(.subheadline) .foregroundStyle(Color.textSecondary) @@ -394,20 +393,6 @@ public struct HomeView: View { store.send(.view(.tapTodoCategory(todoCategory))) } } - private func categoryIconBackground(_ item: TodoCategoryItem) -> Color { - if colorScheme == .dark { - return item.color - } - - return item.color.opacity(0.12) - } - private func categoryIconForeground(_ item: TodoCategoryItem) -> Color { - if colorScheme == .dark { - return .white - } - - return item.color - } private func refreshDevelopmentGoals() { store.send(.view(.fetchData)) } diff --git a/Application/Presentation/PresentationShared/Resources/Localizable.xcstrings b/Application/Presentation/PresentationShared/Resources/Localizable.xcstrings index 98fbec8f..1969d187 100644 --- a/Application/Presentation/PresentationShared/Resources/Localizable.xcstrings +++ b/Application/Presentation/PresentationShared/Resources/Localizable.xcstrings @@ -2699,6 +2699,23 @@ } } }, + "settings_account_section" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Account" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "계정" + } + } + } + }, "settings_alert_delete_account_message" : { "extractionState" : "manual", "localizations" : { @@ -2784,6 +2801,23 @@ } } }, + "settings_delete_account_subtitle" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "All data will be deleted." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모든 데이터가 삭제됩니다." + } + } + } + }, "settings_delete_account_action" : { "extractionState" : "manual", "localizations" : { @@ -2801,6 +2835,40 @@ } } }, + "settings_app_section" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Settings" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "앱 설정" + } + } + } + }, + "settings_information_section" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Storage & Information" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "저장 공간 및 정보" + } + } + } + }, "settings_join_beta" : { "extractionState" : "manual", "localizations" : { diff --git a/Application/Presentation/PresentationShared/Sources/Common/Component/NavigationBackButton.swift b/Application/Presentation/PresentationShared/Sources/Common/Component/NavigationBackButton.swift new file mode 100644 index 00000000..c0020d53 --- /dev/null +++ b/Application/Presentation/PresentationShared/Sources/Common/Component/NavigationBackButton.swift @@ -0,0 +1,32 @@ +// +// NavigationBackButton.swift +// PresentationShared +// +// Created by opfic on 9/24/26. +// + +import SwiftUI + +public struct NavigationBackButton: View { + private let action: () -> Void + + public init(action: @escaping () -> Void) { + self.action = action + } + + public var body: some View { + if #available(iOS 26.0, *) { + Button(action: action) { + Image(systemName: "chevron.left") + } + .topBarButtonStyle() + } else { + Button(action: action) { + Image(systemName: "chevron.left") + .font(.title3.weight(.semibold)) + .frame(width: 28, height: 28) + } + .adaptiveButtonStyle(shape: .circle, color: .surface, glassEffect: .enabled) + } + } +} diff --git a/Application/Presentation/PresentationShared/Sources/Extension/View+IconStyle.swift b/Application/Presentation/PresentationShared/Sources/Extension/View+IconStyle.swift new file mode 100644 index 00000000..2437c8d7 --- /dev/null +++ b/Application/Presentation/PresentationShared/Sources/Extension/View+IconStyle.swift @@ -0,0 +1,32 @@ +// +// View+IconStyle.swift +// PresentationShared +// +// Created by opfic on 9/24/26. +// + +import SwiftUI + +public extension View { + func iconStyle( + color: Color, + in shape: S + ) -> some View { + modifier(IconStyleModifier(color: color, shape: shape)) + } +} + +private struct IconStyleModifier: ViewModifier { + @Environment(\.colorScheme) private var colorScheme + let color: Color + let shape: S + + func body(content: Content) -> some View { + content + .foregroundStyle(colorScheme == .dark ? Color.white : color) + .background( + colorScheme == .dark ? color : color.opacity(0.12), + in: shape + ) + } +} diff --git a/Application/Presentation/ProfileTab/Sources/Profile/GoalSummaryCard.swift b/Application/Presentation/ProfileTab/Sources/Profile/GoalSummaryCard.swift index 7220ebdc..15deedb1 100644 --- a/Application/Presentation/ProfileTab/Sources/Profile/GoalSummaryCard.swift +++ b/Application/Presentation/ProfileTab/Sources/Profile/GoalSummaryCard.swift @@ -102,9 +102,8 @@ struct GoalSummaryCard: View { VStack(spacing: 8) { Image(systemName: symbol) .font(.subheadline.weight(.semibold)) - .foregroundStyle(color) .frame(width: 36, height: 36) - .background(Color.surface, in: .circle) + .iconStyle(color: color, in: Circle()) Text(title, bundle: PresentationResources.bundle) .font(.caption) .foregroundStyle(Color.textSecondary) diff --git a/Application/Presentation/ProfileTab/Sources/Profile/ProfileView.swift b/Application/Presentation/ProfileTab/Sources/Profile/ProfileView.swift index 2f3d2fa9..1a293234 100644 --- a/Application/Presentation/ProfileTab/Sources/Profile/ProfileView.swift +++ b/Application/Presentation/ProfileTab/Sources/Profile/ProfileView.swift @@ -91,22 +91,12 @@ public struct ProfileView: View { Text("nav_profile", bundle: PresentationResources.bundle) .font(.largeTitle.bold()) Spacer() - if #available(iOS 26.0, *) { - Button { - path.append(.settings) - } label: { - Image(systemName: "gearshape") - } - .topBarButtonStyle() - } else { - Button { - path.append(.settings) - } label: { - Image(systemName: "gearshape") - .foregroundStyle(Color.textTertiary) - } - .adaptiveButtonStyle() + Button { + path.append(.settings) + } label: { + Image(systemName: "gearshape") } + .topBarButtonStyle() } } .padding(.bottom, 8) diff --git a/Application/Presentation/ProfileTab/Sources/Profile/RecentActivityCard.swift b/Application/Presentation/ProfileTab/Sources/Profile/RecentActivityCard.swift index f8a11522..b561148d 100644 --- a/Application/Presentation/ProfileTab/Sources/Profile/RecentActivityCard.swift +++ b/Application/Presentation/ProfileTab/Sources/Profile/RecentActivityCard.swift @@ -55,9 +55,11 @@ private struct RecentTodoCard: View { HStack(spacing: 12) { Image(systemName: category.symbolName) .font(.title3.weight(.semibold)) - .foregroundStyle(category.color) .frame(width: iconSize, height: iconSize) - .background(category.color.opacity(0.1), in: RoundedRectangle(cornerRadius: 12)) + .iconStyle( + color: category.color, + in: RoundedRectangle(cornerRadius: 12) + ) .accessibilityHidden(true) VStack(alignment: .leading, spacing: 8) { diff --git a/Application/Presentation/ProfileTab/Sources/Settings/AccountCard.swift b/Application/Presentation/ProfileTab/Sources/Settings/AccountCard.swift new file mode 100644 index 00000000..f1eab7a6 --- /dev/null +++ b/Application/Presentation/ProfileTab/Sources/Settings/AccountCard.swift @@ -0,0 +1,92 @@ +// +// AccountCard.swift +// ProfileTab +// +// Created by opfic on 9/24/26. +// + +import SwiftUI +import PresentationShared + +struct AccountCard: View { + @ScaledMetric(relativeTo: .headline) private var iconSize = 36 + let isNetworkConnected: Bool + let isLoading: Bool + let showsSignOutProgress: Bool + let onAccount: () -> Void + let onSignOut: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("settings_account_section", bundle: PresentationResources.bundle) + .font(.title3.bold()) + .foregroundStyle(Color.primary) + + VStack(spacing: 0) { + Button(action: onAccount) { + HStack(spacing: 12) { + Image(systemName: "person.crop.circle") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String(localized: "settings_account", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.primary) + + Spacer(minLength: 8) + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .opacity(isNetworkConnected ? 1 : 0.5) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + .disabled(!isNetworkConnected) + + Divider() + .padding(.leading, 64) + .padding(.trailing, 16) + .accessibilityHidden(true) + + Button(action: onSignOut) { + HStack(spacing: 12) { + Image(systemName: "rectangle.portrait.and.arrow.right") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .danger, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String(localized: "settings_sign_out", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.danger) + + Spacer(minLength: 8) + + if showsSignOutProgress { + ProgressView() + } + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .opacity(isNetworkConnected ? 1 : 0.5) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + .disabled(!isNetworkConnected || isLoading) + } + .background(Color.surface, in: .rect(cornerRadius: 16)) + } + } +} diff --git a/Application/Presentation/ProfileTab/Sources/Settings/AppInformationCard.swift b/Application/Presentation/ProfileTab/Sources/Settings/AppInformationCard.swift new file mode 100644 index 00000000..c867129d --- /dev/null +++ b/Application/Presentation/ProfileTab/Sources/Settings/AppInformationCard.swift @@ -0,0 +1,128 @@ +// +// AppInformationCard.swift +// ProfileTab +// +// Created by opfic on 9/24/26. +// + +import SwiftUI +import PresentationShared + +struct AppInformationCard: View { + @ScaledMetric(relativeTo: .headline) private var iconSize = 36 + let appVersion: String? + let privacyPolicyURL: URL? + let betaTestURL: URL? + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("settings_information_section", bundle: PresentationResources.bundle) + .font(.title3.bold()) + .foregroundStyle(Color.primary) + + VStack(spacing: 0) { + if let appVersion { + HStack(spacing: 12) { + Image(systemName: "info.circle") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String(localized: "settings_version", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.primary) + + Spacer(minLength: 8) + + Text(appVersion) + .foregroundStyle(Color.textSecondary) + .multilineTextAlignment(.trailing) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .accessibilityElement(children: .combine) + } + + if let privacyPolicyURL { + if appVersion != nil { + Divider() + .padding(.leading, 64) + .padding(.trailing, 16) + .accessibilityHidden(true) + } + + Link(destination: privacyPolicyURL) { + HStack(spacing: 12) { + Image(systemName: "doc.text") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String( + localized: "settings_privacy_policy", + bundle: PresentationResources.bundle + )) + .foregroundStyle(Color.primary) + + Spacer(minLength: 8) + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + } + + if let betaTestURL { + if appVersion != nil || privacyPolicyURL != nil { + Divider() + .padding(.leading, 64) + .padding(.trailing, 16) + .accessibilityHidden(true) + } + + Link(destination: betaTestURL) { + HStack(spacing: 12) { + Image(systemName: "flask") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 4) { + Text(String(localized: "settings_join_beta", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.primary) + Text(String( + localized: "settings_join_beta_subtitle", + bundle: PresentationResources.bundle + )) + .font(.footnote) + .foregroundStyle(Color.textSecondary) + } + + Spacer(minLength: 8) + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + } + } + .background(Color.surface, in: .rect(cornerRadius: 16)) + } + } +} diff --git a/Application/Presentation/ProfileTab/Sources/Settings/AppSettingsCard.swift b/Application/Presentation/ProfileTab/Sources/Settings/AppSettingsCard.swift new file mode 100644 index 00000000..b255fb25 --- /dev/null +++ b/Application/Presentation/ProfileTab/Sources/Settings/AppSettingsCard.swift @@ -0,0 +1,89 @@ +// +// AppSettingsCard.swift +// ProfileTab +// +// Created by opfic on 9/24/26. +// + +import SwiftUI +import PresentationShared + +struct AppSettingsCard: View { + @ScaledMetric(relativeTo: .headline) private var iconSize = 36 + let themeName: String + let isNetworkConnected: Bool + let onTheme: () -> Void + let onNotifications: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("settings_app_section", bundle: PresentationResources.bundle) + .font(.title3.bold()) + .foregroundStyle(Color.primary) + + VStack(spacing: 0) { + Button(action: onTheme) { + HStack(spacing: 12) { + Image(systemName: "desktopcomputer") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String(localized: "settings_theme", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.primary) + + Spacer(minLength: 8) + + Text(themeName) + .foregroundStyle(Color.textSecondary) + .multilineTextAlignment(.trailing) + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + + Divider() + .padding(.leading, 64) + .padding(.trailing, 16) + .accessibilityHidden(true) + + Button(action: onNotifications) { + HStack(spacing: 12) { + Image(systemName: "bell") + .font(.headline) + .frame(width: iconSize, height: iconSize) + .iconStyle(color: .accent, in: RoundedRectangle(cornerRadius: 10)) + .accessibilityHidden(true) + + Text(String(localized: "settings_notifications", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.primary) + + Spacer(minLength: 8) + + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(Color.textSecondary) + .accessibilityHidden(true) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .opacity(isNetworkConnected ? 1 : 0.5) + .accessibilityElement(children: .combine) + } + .buttonStyle(.plain) + .disabled(!isNetworkConnected) + } + .background(Color.surface, in: .rect(cornerRadius: 16)) + } + } +} diff --git a/Application/Presentation/ProfileTab/Sources/Settings/SettingsView.swift b/Application/Presentation/ProfileTab/Sources/Settings/SettingsView.swift index 55b5dd38..e77642ae 100644 --- a/Application/Presentation/ProfileTab/Sources/Settings/SettingsView.swift +++ b/Application/Presentation/ProfileTab/Sources/Settings/SettingsView.swift @@ -10,104 +10,100 @@ import Domain import PresentationShared struct SettingsView: View { + @Environment(\.dismiss) private var dismiss + @ScaledMetric(relativeTo: .body) private var deleteAccountButtonHeight = CGFloat(22) @Bindable var store: StoreOf let onNavigate: (ProfileRoute) -> Void + private var privacyPolicyURL: URL? { + guard let policyString = store.policyURL else { return nil } + return URL(string: policyString) + } + var body: some View { - let connected = store.isNetworkConnected - Form { - Section { - Button { - onNavigate(.theme) - } label: { - HStack { - Text(String(localized: "settings_theme", bundle: PresentationResources.bundle)) - .foregroundStyle(Color.primary) - Spacer() - Text(store.theme.localizedName(in: PresentationResources.bundle)) - .foregroundStyle(Color.gray) + ScrollView { + LazyVStack(alignment: .leading, spacing: 24, pinnedViews: [.sectionHeaders]) { + Section { + AppSettingsCard( + themeName: store.theme.localizedName(in: PresentationResources.bundle), + isNetworkConnected: store.isNetworkConnected, + onTheme: { onNavigate(.theme) }, + onNotifications: { onNavigate(.pushNotification) } + ) + + if store.appVersion != nil || privacyPolicyURL != nil || store.betaTestURL != nil { + AppInformationCard( + appVersion: store.appVersion, + privacyPolicyURL: privacyPolicyURL, + betaTestURL: store.betaTestURL + ) } - } - Button { - onNavigate(.pushNotification) - } label: { - Text(String(localized: "settings_notifications", bundle: PresentationResources.bundle)) - .foregroundStyle(connected ? Color.primary : Color.secondary) - } - .disabled(!connected) + AccountCard( + isNetworkConnected: store.isNetworkConnected, + isLoading: store.isLoading, + showsSignOutProgress: store.activeLoadingRow == .signOut, + onAccount: { onNavigate(.account) }, + onSignOut: { store.send(.setAlert(.signOut)) } + ) - } - - Section { - if let appVersion = store.appVersion { - HStack { - Text(String(localized: "settings_version", bundle: PresentationResources.bundle)) - Spacer() - Text(appVersion) - } - } - if let policyString = store.policyURL, - let url = URL(string: policyString) { - Link(destination: url) { - Text(String(localized: "settings_privacy_policy", bundle: PresentationResources.bundle)) - .foregroundColor(Color.blue) - } - } - if let betaTestURL = store.betaTestURL { - Link(destination: betaTestURL) { - VStack(alignment: .leading) { - Text(String(localized: "settings_join_beta", bundle: PresentationResources.bundle)) - .foregroundStyle(Color.primary) - Text(String(localized: "settings_join_beta_subtitle", bundle: PresentationResources.bundle)) - .foregroundStyle(Color.gray) - .font(.caption) - } - } + deleteAccountContent + } header: { + topBar } } - - Section { - Button { - onNavigate(.account) - } label: { - Text(String(localized: "settings_account", bundle: PresentationResources.bundle)) - } - .disabled(!connected) - Button { - store.send(.setAlert(.signOut)) - } label: { - HStack { - Text(String(localized: "settings_sign_out", bundle: PresentationResources.bundle)) - .foregroundStyle(.red) - Spacer() - if store.activeLoadingRow == .signOut { - ProgressView() - } - } - } - .disabled(!connected || store.isLoading) - } - + .toolbarVisibility(.hidden, for: .navigationBar) + .padding(.horizontal, 16) + .padding(.bottom, 24) + } + .background(Color.appBackground) + .prominentAlert(store, state: \.alert, action: \.alert) + } + + private var topBar: some View { + ZStack { + Text(String(localized: "nav_settings", bundle: PresentationResources.bundle)) + .font(.headline) + HStack { + NavigationBackButton(action: dismiss.callAsFunction) Spacer() - Button { - store.send(.setAlert(.deleteAuth)) - } label: { + } + } + .padding(.bottom, 12) + .background(Color.appBackground) + .toolbarBackground(Color.appBackground) + } + + private var deleteAccountContent: some View { + VStack(spacing: 16) { + Button { + store.send(.setAlert(.deleteAuth)) + } label: { + Group { if store.activeLoadingRow == .deleteAuth { ProgressView() + .tint(Color.danger) } else { Text(String(localized: "settings_delete_account", bundle: PresentationResources.bundle)) - .foregroundStyle(.red) - .font(.headline) + .bold() + .foregroundStyle(Color.danger) } } - .disabled(!connected || store.isLoading) - Spacer() + .font(.system(.body)) + .contentShape(.rect(cornerRadius: 12)) + .frame(maxWidth: .infinity) + .frame(height: deleteAccountButtonHeight + 24) + .background(Color.surface, in: .rect(cornerRadius: 12)) } + .buttonStyle(.plain) + .disabled(!store.isNetworkConnected || store.isLoading) + + Text("settings_delete_account_subtitle", bundle: PresentationResources.bundle) + .font(.footnote) + .foregroundStyle(Color.textSecondary) } - .navigationTitle(String(localized: "nav_settings", bundle: PresentationResources.bundle)) - .navigationBarTitleDisplayMode(.inline) - .prominentAlert(store, state: \.alert, action: \.alert) + .frame(maxWidth: .infinity) + .padding(.top, 24) } }