Improve migrate design (#885)

- Mention iCloud in informational message
- Redesign to always show message despite no migratable profiles
- Refine the looks
This commit is contained in:
Davide 2024-11-17 14:02:40 +01:00 committed by GitHub
parent 1ddea2ad5e
commit c93a43702c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 68 additions and 46 deletions

View File

@ -46,8 +46,9 @@ extension MigrateContentView {
private var selection: Set<UUID> = [] private var selection: Set<UUID> = []
var body: some View { var body: some View {
List { VStack {
profilesSection messageView
profilesList
} }
.themeNavigationDetail() .themeNavigationDetail()
.toolbar { .toolbar {
@ -61,38 +62,43 @@ extension MigrateContentView {
} }
private extension MigrateContentView.ListView { private extension MigrateContentView.ListView {
var actionsHeader: some View { var isEmpty: Bool {
VStack(alignment: .leading, spacing: 16) { step.isReady && profiles.isEmpty
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 profilesSection: some View { var messageView: some View {
Section { Text(Strings.Views.Migrate.Sections.Main.header)
ForEach(profiles, id: \.id) { .padding([.top, .leading, .trailing])
if isEditing { }
EditableRowView(profile: $0, selection: $selection)
} else { var profilesList: some View {
ControlView( List {
step: step, Section {
profile: $0, ForEach(profiles, id: \.id) {
isIncluded: isIncludedBinding(for: $0.id), if isEditing {
status: statusBinding(for: $0.id) 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) .disabled(!step.canSelect)
.themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles)
} }
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> { func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {

View File

@ -44,11 +44,10 @@ extension MigrateContentView {
let performButton: () -> PerformButton let performButton: () -> PerformButton
var body: some View { var body: some View {
Form { VStack(spacing: .zero) {
messageSection messageView
profilesSection profilesForm
} }
.themeForm()
.toolbar { .toolbar {
ToolbarItem(placement: .confirmationAction, content: performButton) ToolbarItem(placement: .confirmationAction, content: performButton)
} }
@ -57,14 +56,17 @@ extension MigrateContentView {
} }
private extension MigrateContentView.TableView { private extension MigrateContentView.TableView {
var messageSection: some View { var isEmpty: Bool {
Section { step.isReady && profiles.isEmpty
Text(Strings.Views.Migrate.Sections.Main.header)
}
} }
var profilesSection: some View { var messageView: some View {
Section { Text(Strings.Views.Migrate.Sections.Main.header)
.padding([.top, .leading, .trailing])
}
var profilesForm: some View {
Form {
Table(profiles) { Table(profiles) {
TableColumn(Strings.Global.name) { TableColumn(Strings.Global.name) {
Text($0.name) Text($0.name)
@ -95,6 +97,8 @@ private extension MigrateContentView.TableView {
} }
} }
.disabled(!step.canSelect) .disabled(!step.canSelect)
.themeForm()
.themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles)
} }
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> { func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {

View File

@ -45,6 +45,12 @@ extension MigrateView {
} }
} }
extension MigrateViewStep {
var isReady: Bool {
![.initial, .fetching].contains(self)
}
}
extension MigrateView.Model { extension MigrateView.Model {
var visibleProfiles: [MigratableProfile] { var visibleProfiles: [MigratableProfile] {
profiles profiles

View File

@ -74,11 +74,7 @@ struct MigrateView: View {
onDelete: onDelete, onDelete: onDelete,
performButton: performButton performButton: performButton
) )
.themeProgress( .themeProgress(if: !model.step.isReady)
if: [.initial, .fetching].contains(model.step),
isEmpty: model.profiles.isEmpty,
emptyMessage: Strings.Views.Migrate.noProfiles
)
.themeAnimation(on: model, category: .profiles) .themeAnimation(on: model, category: .profiles)
.themeConfirmation( .themeConfirmation(
isPresented: $isDeleting, isPresented: $isDeleting,

View File

@ -744,8 +744,8 @@ public enum Strings {
} }
public enum Sections { public enum Sections {
public enum Main { 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. /// 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. 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. 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.")
} }
} }
} }

View File

@ -163,7 +163,7 @@
"views.migrate.no_profiles" = "Nothing to migrate"; "views.migrate.no_profiles" = "Nothing to migrate";
"views.migrate.items.discard" = "Discard"; "views.migrate.items.discard" = "Discard";
"views.migrate.items.migrate" = "Proceed"; "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.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"; "views.donate.title" = "Make a donation";

View File

@ -179,6 +179,16 @@ extension View {
modifier(ThemeAnimationModifier(value: value, category: category)) 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 { public func themeProgress(if isProgressing: Bool) -> some View {
modifier(ThemeProgressViewModifier(isProgressing: isProgressing) { modifier(ThemeProgressViewModifier(isProgressing: isProgressing) {
EmptyView() EmptyView()