Improve migrate header on iOS (#881)

Refactor into more subviews.
This commit is contained in:
Davide 2024-11-16 15:07:47 +01:00 committed by GitHub
parent 589f2f62e0
commit afa22719bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 94 deletions

View File

@ -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<Bool> {
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<UUID>
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

View File

@ -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<Bool> {
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) ?? ""
}
}

View File

@ -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,