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:
parent
1ddea2ad5e
commit
c93a43702c
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue