From c93a43702c655fe7039077981bfe21152fb71ca0 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 17 Nov 2024 14:02:40 +0100 Subject: [PATCH] Improve migrate design (#885) - Mention iCloud in informational message - Redesign to always show message despite no migratable profiles - Refine the looks --- .../Migration/MigrateContentView+List.swift | 62 ++++++++++--------- .../Migration/MigrateContentView+Table.swift | 24 ++++--- .../Views/Migration/MigrateView+Model.swift | 6 ++ .../Views/Migration/MigrateView.swift | 6 +- .../UILibrary/L10n/SwiftGen+Strings.swift | 4 +- .../Resources/en.lproj/Localizable.strings | 2 +- .../UILibrary/Theme/UI/Theme+Modifiers.swift | 10 +++ 7 files changed, 68 insertions(+), 46 deletions(-) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift index 04d000f3..864de332 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+List.swift @@ -46,8 +46,9 @@ extension MigrateContentView { private var selection: Set = [] var body: some View { - List { - profilesSection + VStack { + messageView + profilesList } .themeNavigationDetail() .toolbar { @@ -61,38 +62,43 @@ extension MigrateContentView { } private extension MigrateContentView.ListView { - 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 - } - } - .textCase(.none) - .listRowInsets(.init(top: 8, leading: 0, bottom: 8, trailing: 0)) + var isEmpty: Bool { + step.isReady && profiles.isEmpty } - 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) - ) + var messageView: some View { + Text(Strings.Views.Migrate.Sections.Main.header) + .padding([.top, .leading, .trailing]) + } + + var profilesList: some View { + List { + 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: { + EditProfilesButton(isEditing: $isEditing, selection: $selection) { + onDelete(profiles.filter { + selection.contains($0.id) + }) + // disable isEditing after confirmation + } + .textCase(.none) } - } header: { - actionsHeader } + .listStyle(.plain) .disabled(!step.canSelect) + .themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles) } func isIncludedBinding(for profileId: UUID) -> Binding { diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift index 8ede4458..b2495387 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateContentView+Table.swift @@ -44,11 +44,10 @@ extension MigrateContentView { let performButton: () -> PerformButton var body: some View { - Form { - messageSection - profilesSection + VStack(spacing: .zero) { + messageView + profilesForm } - .themeForm() .toolbar { ToolbarItem(placement: .confirmationAction, content: performButton) } @@ -57,14 +56,17 @@ extension MigrateContentView { } private extension MigrateContentView.TableView { - var messageSection: some View { - Section { - Text(Strings.Views.Migrate.Sections.Main.header) - } + var isEmpty: Bool { + step.isReady && profiles.isEmpty } - var profilesSection: some View { - Section { + var messageView: some View { + Text(Strings.Views.Migrate.Sections.Main.header) + .padding([.top, .leading, .trailing]) + } + + var profilesForm: some View { + Form { Table(profiles) { TableColumn(Strings.Global.name) { Text($0.name) @@ -95,6 +97,8 @@ private extension MigrateContentView.TableView { } } .disabled(!step.canSelect) + .themeForm() + .themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles) } func isIncludedBinding(for profileId: UUID) -> Binding { diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Model.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Model.swift index 92a8d10c..61c38eb1 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Model.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Model.swift @@ -45,6 +45,12 @@ extension MigrateView { } } +extension MigrateViewStep { + var isReady: Bool { + ![.initial, .fetching].contains(self) + } +} + extension MigrateView.Model { var visibleProfiles: [MigratableProfile] { profiles diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView.swift index 50e81448..bcd546c8 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView.swift @@ -74,11 +74,7 @@ struct MigrateView: View { onDelete: onDelete, performButton: performButton ) - .themeProgress( - if: [.initial, .fetching].contains(model.step), - isEmpty: model.profiles.isEmpty, - emptyMessage: Strings.Views.Migrate.noProfiles - ) + .themeProgress(if: !model.step.isReady) .themeAnimation(on: model, category: .profiles) .themeConfirmation( isPresented: $isDeleting, diff --git a/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift b/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift index e74a4ad2..1dd8b405 100644 --- a/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift +++ b/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift @@ -744,8 +744,8 @@ public enum Strings { } public enum Sections { public enum Main { - /// Select below the profiles from old versions of Passepartout that you want to import. They will disappear from this list once imported successfully. - public static let header = Strings.tr("Localizable", "views.migrate.sections.main.header", fallback: "Select below the profiles from old versions of Passepartout that you want to import. They will disappear from this list once imported successfully.") + /// Select below the profiles from old versions of Passepartout that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later. + public static let header = Strings.tr("Localizable", "views.migrate.sections.main.header", fallback: "Select below the profiles from old versions of Passepartout that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.") } } } diff --git a/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings b/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings index fd1d0df3..8f6794c2 100644 --- a/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings @@ -163,7 +163,7 @@ "views.migrate.no_profiles" = "Nothing to migrate"; "views.migrate.items.discard" = "Discard"; "views.migrate.items.migrate" = "Proceed"; -"views.migrate.sections.main.header" = "Select below the profiles from old versions of Passepartout that you want to import. They will disappear from this list once imported successfully."; +"views.migrate.sections.main.header" = "Select below the profiles from old versions of Passepartout that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later."; "views.migrate.alerts.delete.message" = "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@"; "views.donate.title" = "Make a donation"; diff --git a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift index b8558669..8e07b992 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift @@ -179,6 +179,16 @@ extension View { modifier(ThemeAnimationModifier(value: value, category: category)) } + @ViewBuilder + public func themeEmpty(if isEmpty: Bool, message: String) -> some View { + if !isEmpty { + self + } else { + Text(message) + .themeEmptyMessage() + } + } + public func themeProgress(if isProgressing: Bool) -> some View { modifier(ThemeProgressViewModifier(isProgressing: isProgressing) { EmptyView()