parent
589f2f62e0
commit
afa22719bf
|
@ -47,27 +47,9 @@ extension MigrateContentView {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
Section {
|
profilesSection
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
.themeNavigationDetail()
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
performButton()
|
performButton()
|
||||||
|
@ -79,36 +61,40 @@ extension MigrateContentView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MigrateContentView.ListView {
|
private extension MigrateContentView.ListView {
|
||||||
var editButton: some View {
|
var actionsHeader: some View {
|
||||||
HStack {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
if isEditing {
|
Text(Strings.Views.Migrate.Sections.Main.header)
|
||||||
Button(Strings.Global.cancel) {
|
EditProfilesButton(isEditing: $isEditing, selection: $selection) {
|
||||||
isEditing = false
|
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> {
|
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {
|
||||||
Binding {
|
Binding {
|
||||||
statuses[profileId] != .excluded
|
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 {
|
private extension MigrateContentView.ListView {
|
||||||
struct EditableRowView: View {
|
struct EditableRowView: View {
|
||||||
let profile: MigratableProfile
|
let profile: MigratableProfile
|
||||||
|
|
|
@ -45,40 +45,8 @@ extension MigrateContentView {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
messageSection
|
||||||
Text(Strings.Views.Migrate.Sections.Main.header)
|
profilesSection
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
.themeForm()
|
.themeForm()
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -89,6 +57,46 @@ extension MigrateContentView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MigrateContentView.TableView {
|
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> {
|
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {
|
||||||
Binding {
|
Binding {
|
||||||
statuses[profileId] != .excluded
|
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 {
|
private extension MigrateContentView.TableView {
|
||||||
struct ControlView: View {
|
struct ControlView: View {
|
||||||
let step: MigrateViewStep
|
let step: MigrateViewStep
|
||||||
|
@ -143,9 +159,3 @@ private extension MigrateContentView.TableView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MigratableProfile {
|
|
||||||
var timestamp: String {
|
|
||||||
lastUpdate?.localizedDescription(style: .timestamp) ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -101,15 +101,7 @@ extension AppContext {
|
||||||
profilesContainerName: Constants.shared.containers.legacyV2,
|
profilesContainerName: Constants.shared.containers.legacyV2,
|
||||||
cloudKitIdentifier: BundleConfiguration.mainString(for: .legacyV2CloudKitId)
|
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)
|
let migrationManager = MigrationManager(profileStrategy: profileStrategy)
|
||||||
#endif
|
|
||||||
|
|
||||||
return AppContext(
|
return AppContext(
|
||||||
iapManager: .sharedForApp,
|
iapManager: .sharedForApp,
|
||||||
|
|
Loading…
Reference in New Issue