diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift index 3ff05a21..c4de9d3f 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift @@ -47,27 +47,9 @@ extension MigrateContentView { var body: some View { List { - Section { - Text(Strings.Views.Migrate.Sections.Main.header) - } - Section { - ForEach(profiles, id: \.id) { - if isEditing { - EditableRowView(profile: $0, selection: $selection) - } else { - ControlView( - step: step, - profile: $0, - isIncluded: isIncludedBinding(for: $0.id), - status: statusBinding(for: $0.id) - ) - } - } - } header: { - editButton - } - .disabled(!step.canSelect) + profilesSection } + .themeNavigationDetail() .toolbar { ToolbarItem(placement: .confirmationAction) { performButton() @@ -79,36 +61,40 @@ extension MigrateContentView { } private extension MigrateContentView.ListView { - var editButton: some View { - HStack { - if isEditing { - Button(Strings.Global.cancel) { - isEditing = false - } + var actionsHeader: some View { + VStack(alignment: .leading, spacing: 16) { + Text(Strings.Views.Migrate.Sections.Main.header) + EditProfilesButton(isEditing: $isEditing, selection: $selection) { + onDelete(profiles.filter { + selection.contains($0.id) + }) + // disable isEditing after confirmation } - Spacer() - Button(isEditing ? Strings.Global.delete : Strings.Global.edit, role: isEditing ? .destructive : nil) { - if isEditing { - if !selection.isEmpty { - onDelete(profiles.filter { - selection.contains($0.id) - }) - // disable isEditing after confirmation - } else { - isEditing = false - } - } else { - selection = [] - isEditing = true - } - } - .disabled(isEditing && selection.isEmpty) } - .frame(height: 30) + .textCase(.none) + .listRowInsets(.init(top: 8, leading: 0, bottom: 8, trailing: 0)) + } + + var profilesSection: some View { + Section { + ForEach(profiles, id: \.id) { + if isEditing { + EditableRowView(profile: $0, selection: $selection) + } else { + ControlView( + step: step, + profile: $0, + isIncluded: isIncludedBinding(for: $0.id), + status: statusBinding(for: $0.id) + ) + } + } + } header: { + actionsHeader + } + .disabled(!step.canSelect) } -} -private extension MigrateContentView.ListView { func isIncludedBinding(for profileId: UUID) -> Binding { Binding { statuses[profileId] != .excluded @@ -134,6 +120,46 @@ private extension MigrateContentView.ListView { } } +// MARK: - Subviews + +private extension MigrateContentView.ListView { + struct EditProfilesButton: View { + + @Binding + var isEditing: Bool + + @Binding + var selection: Set + + let onDelete: () -> Void + + var body: some View { + HStack { + if isEditing { + Button(Strings.Global.cancel) { + isEditing = false + } + } + Spacer() + Button(isEditing ? Strings.Global.delete : Strings.Global.edit, role: isEditing ? .destructive : nil) { + if isEditing { + if !selection.isEmpty { + onDelete() + } else { + isEditing = false + } + } else { + selection = [] + isEditing = true + } + } + .disabled(isEditing && selection.isEmpty) + } + .frame(height: 30) + } + } +} + private extension MigrateContentView.ListView { struct EditableRowView: View { let profile: MigratableProfile diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift index 150c84f9..91dcb942 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift @@ -45,40 +45,8 @@ extension MigrateContentView { var body: some View { Form { - Section { - Text(Strings.Views.Migrate.Sections.Main.header) - } - Section { - Table(profiles) { - TableColumn(Strings.Global.name) { - Text($0.name) - .foregroundStyle(statuses.style(for: $0.id)) - } - TableColumn(Strings.Global.lastUpdate) { - Text($0.timestamp) - .foregroundStyle(statuses.style(for: $0.id)) - } - TableColumn("") { - ControlView( - step: step, - isIncluded: isIncludedBinding(for: $0.id), - status: statuses[$0.id] - ) - .environmentObject(theme) // TODO: #873, Table loses environment - } - .width(20) - TableColumn("") { profile in - Button { - onDelete([profile]) - } label: { - ThemeImage(.editableSectionRemove) - } - .environmentObject(theme) // TODO: #873, Table loses environment - } - .width(20) - } - } - .disabled(!step.canSelect) + messageSection + profilesSection } .themeForm() .toolbar { @@ -89,6 +57,46 @@ extension MigrateContentView { } private extension MigrateContentView.TableView { + var messageSection: some View { + Section { + Text(Strings.Views.Migrate.Sections.Main.header) + } + } + + var profilesSection: some View { + Section { + Table(profiles) { + TableColumn(Strings.Global.name) { + Text($0.name) + .foregroundStyle(statuses.style(for: $0.id)) + } + TableColumn(Strings.Global.lastUpdate) { + Text($0.timestamp) + .foregroundStyle(statuses.style(for: $0.id)) + } + TableColumn("") { + ControlView( + step: step, + isIncluded: isIncludedBinding(for: $0.id), + status: statuses[$0.id] + ) + .environmentObject(theme) // TODO: #873, Table loses environment + } + .width(20) + TableColumn("") { profile in + Button { + onDelete([profile]) + } label: { + ThemeImage(.editableSectionRemove) + } + .environmentObject(theme) // TODO: #873, Table loses environment + } + .width(20) + } + } + .disabled(!step.canSelect) + } + func isIncludedBinding(for profileId: UUID) -> Binding { Binding { statuses[profileId] != .excluded @@ -102,6 +110,14 @@ private extension MigrateContentView.TableView { } } +private extension MigratableProfile { + var timestamp: String { + lastUpdate?.localizedDescription(style: .timestamp) ?? "" + } +} + +// MARK: - Subviews + private extension MigrateContentView.TableView { struct ControlView: View { let step: MigrateViewStep @@ -143,9 +159,3 @@ private extension MigrateContentView.TableView { } } } - -private extension MigratableProfile { - var timestamp: String { - lastUpdate?.localizedDescription(style: .timestamp) ?? "" - } -} diff --git a/Passepartout/Shared/AppContext+Shared.swift b/Passepartout/Shared/AppContext+Shared.swift index de5bdb7d..8f693149 100644 --- a/Passepartout/Shared/AppContext+Shared.swift +++ b/Passepartout/Shared/AppContext+Shared.swift @@ -101,15 +101,7 @@ extension AppContext { profilesContainerName: Constants.shared.containers.legacyV2, cloudKitIdentifier: BundleConfiguration.mainString(for: .legacyV2CloudKitId) ) -#if DEBUG - let migrationManager = MigrationManager(profileStrategy: profileStrategy, simulation: .init( - fakeProfiles: true, - maxMigrationTime: 3.0, - randomFailures: true - )) -#else let migrationManager = MigrationManager(profileStrategy: profileStrategy) -#endif return AppContext( iapManager: .sharedForApp,