Skip to content

Commit 50d3e89

Browse files
authored
Merge pull request #4 from easydev991/develop
Доработки бэкапа
2 parents d202595 + b5aa438 commit 50d3e89

File tree

6 files changed

+69
-18
lines changed

6 files changed

+69
-18
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Binding+mappedToBool.swift
3+
// SwiftUI-Days
4+
//
5+
// Created by Oleg991 on 06.04.2025.
6+
//
7+
8+
import SwiftUI
9+
10+
extension Binding<Bool> {
11+
init(bindingOptional: Binding<(some Sendable)?>) {
12+
self.init(
13+
get: { bindingOptional.wrappedValue != nil },
14+
set: { newValue in
15+
guard newValue == false else { return }
16+
/// Обрабатываем только значение `false`, чтобы обнулить опционал,
17+
/// потому что не можем восстановить предыдущее состояние опционала для значения `true`
18+
bindingOptional.wrappedValue = nil
19+
}
20+
)
21+
}
22+
}
23+
24+
extension Binding {
25+
func mappedToBool<Wrapped: Sendable>() -> Binding<Bool> where Value == Wrapped? {
26+
Binding<Bool>(bindingOptional: self)
27+
}
28+
}

SwiftUI-Days/Models/BackupFileDocument.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import UniformTypeIdentifiers
1111
struct BackupFileDocument: FileDocument {
1212
static var readableContentTypes: [UTType] { [.json] }
1313
static var writableContentTypes: [UTType] { [.json] }
14-
static func makeBackupItem(with item: Item) -> BackupItem {
14+
static func toBackupItem(item: Item) -> BackupItem {
1515
.init(title: item.title, details: item.details, timestamp: item.timestamp)
1616
}
1717

@@ -35,7 +35,7 @@ struct BackupFileDocument: FileDocument {
3535
}
3636

3737
extension BackupFileDocument {
38-
struct BackupItem: Codable {
38+
struct BackupItem: Codable, Hashable {
3939
let title: String
4040
let details: String
4141
let timestamp: Date

SwiftUI-Days/Models/Item.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ final class Item {
2828
to: .now
2929
).day ?? 0
3030
}
31+
32+
var backupItem: BackupFileDocument.BackupItem {
33+
.init(title: title, details: details, timestamp: timestamp)
34+
}
3135
}
3236

3337
extension Item {

SwiftUI-Days/Screens/Main/MainScreen.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ struct MainScreen: View {
1414
@AppStorage("listSortOrder") private var sortOrder = SortOrder.forward.rawValue
1515
@State private var searchQuery = ""
1616
@State private var editItem: Item?
17+
@State private var path = NavigationPath()
1718

1819
var body: some View {
19-
NavigationStack {
20+
NavigationStack(path: $path) {
2021
ZStack {
2122
if items.isEmpty {
2223
emptyView.transition(.scale.combined(with: .opacity))
@@ -25,6 +26,7 @@ struct MainScreen: View {
2526
}
2627
}
2728
.animation(.bouncy, value: items.isEmpty)
29+
.toolbar(path.isEmpty ? .automatic : .hidden, for: .tabBar)
2830
.navigationTitle("Events")
2931
}
3032
.sheet(isPresented: $showAddItemSheet) {

SwiftUI-Days/Screens/More/AppDataScreen.swift

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ struct AppDataScreen: View {
1414
@State private var showDeleteDataConfirmation = false
1515
@State private var isCreatingBackup = false
1616
@State private var isRestoringFromBackup = false
17-
@State private var showResult = false
1817
@State private var operationResult: OperationResult?
1918

2019
var body: some View {
2120
VStack(spacing: 16) {
2221
Group {
23-
backupDataButton
22+
if !items.isEmpty {
23+
backupDataButton
24+
}
2425
restoreDataButton
2526
if !items.isEmpty {
2627
removeAllDataButton
@@ -32,7 +33,7 @@ struct AppDataScreen: View {
3233
.animation(.default, value: items.isEmpty)
3334
.alert(
3435
operationResult?.title ?? "",
35-
isPresented: $showResult,
36+
isPresented: $operationResult.mappedToBool(),
3637
presenting: operationResult,
3738
actions: { _ in
3839
Button("Ok") {}
@@ -51,7 +52,7 @@ struct AppDataScreen: View {
5152
.accessibilityIdentifier("backupDataButton")
5253
.fileExporter(
5354
isPresented: $isCreatingBackup,
54-
document: BackupFileDocument(items: items.map(BackupFileDocument.makeBackupItem)),
55+
document: BackupFileDocument(items: items.map(BackupFileDocument.toBackupItem)),
5556
contentType: .json,
5657
defaultFilename: "Days backup"
5758
) { result in
@@ -61,7 +62,6 @@ struct AppDataScreen: View {
6162
case let .failure(error):
6263
operationResult = .error(error.localizedDescription)
6364
}
64-
showResult = true
6565
}
6666
}
6767

@@ -77,21 +77,25 @@ struct AppDataScreen: View {
7777
) { result in
7878
switch result {
7979
case let .success(urls):
80-
if let url = urls.first,
81-
url.startAccessingSecurityScopedResource(),
82-
let data = try? Data(contentsOf: url),
83-
let importedItems = try? JSONDecoder().decode([BackupFileDocument.BackupItem].self, from: data) {
80+
guard let url = urls.first, url.startAccessingSecurityScopedResource() else {
81+
operationResult = .failedToRestore
82+
return
83+
}
84+
do {
8485
defer { url.stopAccessingSecurityScopedResource() }
85-
let mappedRealItems = importedItems.map(\.realItem)
86-
mappedRealItems.forEach { modelContext.insert($0) }
86+
let data = try Data(contentsOf: url)
87+
let importedItems = try JSONDecoder().decode([BackupFileDocument.BackupItem].self, from: data)
88+
let currentItems = items.map(\.backupItem)
89+
let newItemsFromBackup = importedItems.filter { !currentItems.contains($0) }
90+
newItemsFromBackup.forEach { modelContext.insert($0.realItem) }
91+
try modelContext.save()
8792
operationResult = .restoreSuccess
88-
} else {
93+
} catch {
8994
operationResult = .failedToRestore
9095
}
9196
case let .failure(error):
9297
operationResult = .error(error.localizedDescription)
9398
}
94-
showResult = true
9599
}
96100
}
97101

@@ -109,10 +113,11 @@ struct AppDataScreen: View {
109113
Button("Delete", role: .destructive) {
110114
do {
111115
try modelContext.delete(model: Item.self)
116+
try modelContext.save()
117+
operationResult = .deletionSuccess
112118
} catch {
113119
assertionFailure(error.localizedDescription)
114120
operationResult = .error(error.localizedDescription)
115-
showResult = true
116121
}
117122
}
118123
.accessibilityIdentifier("confirmRemoveAllDataButton")
@@ -124,12 +129,13 @@ extension AppDataScreen {
124129
private enum OperationResult: Equatable {
125130
case backupSuccess
126131
case restoreSuccess
132+
case deletionSuccess
127133
case failedToRestore
128134
case error(String)
129135

130136
var title: LocalizedStringKey {
131137
switch self {
132-
case .backupSuccess, .restoreSuccess: "Done"
138+
case .backupSuccess, .restoreSuccess, .deletionSuccess: "Done"
133139
case .failedToRestore, .error: "Error"
134140
}
135141
}
@@ -138,6 +144,7 @@ extension AppDataScreen {
138144
switch self {
139145
case .backupSuccess: "Backup data saved"
140146
case .restoreSuccess: "Data restored from backup"
147+
case .deletionSuccess: "All data deleted"
141148
case .failedToRestore: "Unable to recover data from the selected file"
142149
case let .error(message): .init(message)
143150
}

SwiftUI-Days/SupportingFiles/Localizable.xcstrings

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@
103103
}
104104
}
105105
},
106+
"All data deleted" : {
107+
"localizations" : {
108+
"ru" : {
109+
"stringUnit" : {
110+
"state" : "translated",
111+
"value" : "Все данные удалены"
112+
}
113+
}
114+
}
115+
},
106116
"App data" : {
107117
"localizations" : {
108118
"ru" : {

0 commit comments

Comments
 (0)