Skip to content

Commit d6eb93a

Browse files
authored
Merge pull request #23 from tryboxx/SNLB-22/feature/offlineMode
[SNLB-22] Handle offline state
2 parents c1948bf + 4f48ffa commit d6eb93a

File tree

12 files changed

+90
-13
lines changed

12 files changed

+90
-13
lines changed

SnippetsLibrary.xcodeproj/project.pbxproj

+8-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
B82B55802700011000DE4766 /* SnippetsLibraryView+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B557F2700011000DE4766 /* SnippetsLibraryView+Equatable.swift */; };
6767
B82B55822700014600DE4766 /* StartView+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B55812700014600DE4766 /* StartView+Equatable.swift */; };
6868
B82B5586270007C500DE4766 /* DatabaseReference+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B5585270007C500DE4766 /* DatabaseReference+Timeout.swift */; };
69+
B832886D27160F17006BD465 /* SnippetsFetchingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B832886C27160F17006BD465 /* SnippetsFetchingType.swift */; };
70+
B832886E27160F3C006BD465 /* SnippetsFetchingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B832886C27160F17006BD465 /* SnippetsFetchingType.swift */; };
6971
B84BFEA626F56A6A007E5109 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEA526F56A6A007E5109 /* CrashlyticsService.swift */; };
7072
B84BFEA926F57018007E5109 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEA826F57018007E5109 /* System.swift */; };
7173
B84BFEAB26F570CE007E5109 /* CrashlyticsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEAA26F570CE007E5109 /* CrashlyticsError.swift */; };
@@ -218,6 +220,7 @@
218220
B82B557F2700011000DE4766 /* SnippetsLibraryView+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SnippetsLibraryView+Equatable.swift"; sourceTree = "<group>"; };
219221
B82B55812700014600DE4766 /* StartView+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StartView+Equatable.swift"; sourceTree = "<group>"; };
220222
B82B5585270007C500DE4766 /* DatabaseReference+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseReference+Timeout.swift"; sourceTree = "<group>"; };
223+
B832886C27160F17006BD465 /* SnippetsFetchingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetsFetchingType.swift; sourceTree = "<group>"; };
221224
B84BFEA526F56A6A007E5109 /* CrashlyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = "<group>"; };
222225
B84BFEA826F57018007E5109 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
223226
B84BFEAA26F570CE007E5109 /* CrashlyticsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsError.swift; sourceTree = "<group>"; };
@@ -467,6 +470,8 @@
467470
B82561D026E821770040A67E /* ActiveAppView.swift */,
468471
B82561E326E825A40040A67E /* AppDelegate.swift */,
469472
B82561EC26E8C89A0040A67E /* AppSheet.swift */,
473+
B8C549BB26FFA71300720E62 /* AppView.swift */,
474+
B85AC92D2702153E00A008A6 /* AppMenu.swift */,
470475
);
471476
path = Application;
472477
sourceTree = "<group>";
@@ -496,10 +501,8 @@
496501
B82561F626E8C9840040A67E /* SearchBar.swift */,
497502
B8F95B0626EEAEB200335D77 /* SnippetDropCellView.swift */,
498503
B8CE1CAA26FD4193004AD5D5 /* DisabledCommandGroupButton.swift */,
499-
B8C549BB26FFA71300720E62 /* AppView.swift */,
500504
B8BEF5A826FFBF7E0098A778 /* ToastView.swift */,
501505
B8BEF5AC26FFEB600098A778 /* EmptySnippetsListView.swift */,
502-
B85AC92D2702153E00A008A6 /* AppMenu.swift */,
503506
);
504507
path = Views;
505508
sourceTree = "<group>";
@@ -721,6 +724,7 @@
721724
B8F9CA992700F80E0041CE3E /* UploadingStatus.swift */,
722725
B81B087A2702468C00E59F86 /* FileStatusCardType.swift */,
723726
B8546F37270771020043A99F /* SnippetCategory.swift */,
727+
B832886C27160F17006BD465 /* SnippetsFetchingType.swift */,
724728
);
725729
path = Enums;
726730
sourceTree = "<group>";
@@ -967,6 +971,7 @@
967971
buildActionMask = 2147483647;
968972
files = (
969973
B80D24A027090E1A0057582F /* SnippetPlist+Dictonary.swift in Sources */,
974+
B832886E27160F3C006BD465 /* SnippetsFetchingType.swift in Sources */,
970975
B80D24A6270916020057582F /* SmallWidgetView.swift in Sources */,
971976
B80D249C27090E000057582F /* SnippetType.swift in Sources */,
972977
B80D24B2270A72ED0057582F /* SnippetsLibraryWidgetView.swift in Sources */,
@@ -1073,6 +1078,7 @@
10731078
B82561B426E81D570040A67E /* SnippetsLibraryApp.swift in Sources */,
10741079
B88BB45626F55DDB00747631 /* LogsService.swift in Sources */,
10751080
B88D7A6326F7A5C000B114F6 /* SnippetPlist+Dictonary.swift in Sources */,
1081+
B832886D27160F17006BD465 /* SnippetsFetchingType.swift in Sources */,
10761082
B8546F3C27077C310043A99F /* SnippetsLibraryListViewModel.swift in Sources */,
10771083
B8CE1CAB26FD4193004AD5D5 /* DisabledCommandGroupButton.swift in Sources */,
10781084
B85D1A9D26FA8EA50053FF3C /* SnippetsLibraryListSectionView.swift in Sources */,

SnippetsLibrary.xcodeproj/xcshareddata/xcschemes/SnippetsLibrary.xcscheme

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
</TestAction>
5353
<LaunchAction
5454
buildConfiguration = "Release"
55-
selectedDebuggerIdentifier = ""
56-
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
55+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
5757
launchStyle = "0"
5858
useCustomWorkingDirectory = "NO"
5959
ignoresPersistentStateOnLaunch = "NO"

SnippetsLibrary.xcodeproj/xcuserdata/krzysztoflowiec.xcuserdatad/xcschemes/xcschememanagement.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<key>SnippetsLibraryWidgetExtension.xcscheme_^#shared#^_</key>
5555
<dict>
5656
<key>orderHint</key>
57-
<integer>1</integer>
57+
<integer>23</integer>
5858
</dict>
5959
</dict>
6060
<key>SuppressBuildableAutocreation</key>

SnippetsLibrary/Views/AppView.swift renamed to SnippetsLibrary/Application/AppView.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct AppView: View {
5252
switch activeAppView {
5353
case .create:
5454
SnippetsLibraryView(
55-
viewModel: SnippetsLibraryViewModel(),
55+
viewModel: SnippetsLibraryViewModel(hasConnection: $networkObserver.isConnected),
5656
activeSheet: $activeAppSheet
5757
)
5858
.onAppear {
@@ -65,7 +65,10 @@ struct AppView: View {
6565
}
6666
case let .snippetsLibrary(snippetId):
6767
SnippetsLibraryView(
68-
viewModel: SnippetsLibraryViewModel(activeSnippetId: snippetId),
68+
viewModel: SnippetsLibraryViewModel(
69+
activeSnippetId: snippetId,
70+
hasConnection: $networkObserver.isConnected
71+
),
6972
activeSheet: $activeAppSheet
7073
)
7174
.onAppear {

SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListSectionView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ struct SnippetsLibraryListSectionView: View {
2424
Section(header: Text(snippets.first?.type.title ?? Constants.defaultSectionName)) {
2525
ForEach(snippets, id: \.id) {
2626
SnippetListItemView(snippet: $0)
27+
#if DEBUG
2728
.contextMenu {
2829
Button("Delete") {
2930
shouldShowRemoveAlert.toggle()
3031
}
3132
}
33+
#endif
3234
}
3335
}
3436
.tag(snippets.first?.type.rawValue ?? .zero)

SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryView.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ struct SnippetsLibraryView: View {
4848
.onChange(of: viewModel.snippets) { _ in
4949
viewModel.showSnippetPreview()
5050
}
51+
.onChange(of: viewModel.hasConnection) {
52+
viewModel.onChangeConnectionState(hasConnection: $0)
53+
}
5154
.sheet(item: $activeSheet) {
5255
switch $0 {
5356
case let .snippetDetails(snippet, type):
@@ -75,6 +78,6 @@ struct SnippetsLibraryView: View {
7578

7679
struct SnippetsLibraryView_Previews: PreviewProvider {
7780
static var previews: some View {
78-
SnippetsLibraryView(viewModel: SnippetsLibraryViewModel(), activeSheet: .constant(nil))
81+
SnippetsLibraryView(viewModel: SnippetsLibraryViewModel(hasConnection: .constant(true)), activeSheet: .constant(nil))
7982
}
8083
}

SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryViewModel.swift

+28
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class SnippetsLibraryViewModel: ObservableObject {
1515
@Published internal var snippets: [Snippet] = skeletonSnippets
1616
@Published internal var selectedSnippetId: SnippetId?
1717
@Published internal var shouldShowErrorAlert = false
18+
@Published internal var hasConnection: Bool
1819

1920
private let activeSnippetId: SnippetId?
2021

@@ -27,10 +28,12 @@ final class SnippetsLibraryViewModel: ObservableObject {
2728

2829
init(
2930
activeSnippetId: SnippetId? = nil,
31+
hasConnection: Binding<Bool>,
3032
databaseService: DatabaseService = DIContainer.databaseService,
3133
userDefaultsService: UserDefaultsService = DIContainer.userDefaultsService
3234
) {
3335
self.activeSnippetId = activeSnippetId
36+
self.hasConnection = hasConnection.wrappedValue
3437
self.databaseService = databaseService
3538
self.userDefaultsService = userDefaultsService
3639

@@ -60,6 +63,7 @@ final class SnippetsLibraryViewModel: ObservableObject {
6063
case .failure:
6164
self?.shouldShowErrorAlert.toggle()
6265
self?.snippets = []
66+
self?.onChangeConnectionState(hasConnection: false)
6367
}
6468
} receiveValue: { [weak self] in
6569
self?.snippets = $0
@@ -76,6 +80,30 @@ final class SnippetsLibraryViewModel: ObservableObject {
7680
selectedSnippetId = snippetId
7781
}
7882

83+
internal func onChangeConnectionState(hasConnection: Bool) {
84+
if !hasConnection && !snippets.isEmpty && snippets != skeletonSnippets {
85+
userDefaultsService.saveSnippetsLocally(snippets)
86+
} else if !hasConnection && snippets.isEmpty {
87+
fetchLocalSnippets()
88+
}
89+
}
90+
91+
private func fetchLocalSnippets() {
92+
userDefaultsService.fetchSnippets(fetchingType: .local)
93+
.receive(on: DispatchQueue.main)
94+
.sink { [weak self] completion in
95+
switch completion {
96+
case .finished: return
97+
case .failure:
98+
self?.shouldShowErrorAlert.toggle()
99+
self?.snippets = []
100+
}
101+
} receiveValue: { [weak self] in
102+
self?.snippets = $0
103+
}
104+
.store(in: &cancellables)
105+
}
106+
79107
private func removeSnippet(_ snippet: Snippet) {
80108
databaseService.removeSnippet(snippet)
81109
.sink { [weak self] completion in

SnippetsLibrary/Modules/Start/StartViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class StartViewModel: ObservableObject {
4242
}
4343

4444
internal func fetchRecentSnippets() {
45-
userDefaultsService.fetchRecentSnippets()
45+
userDefaultsService.fetchSnippets(fetchingType: .recent)
4646
.receive(on: DispatchQueue.main)
4747
.sink { [weak self] in
4848
self?.recentSnippets = $0

SnippetsLibrary/Modules/Status/StatusViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ final class StatusViewModel: ObservableObject {
3030
// MARK: - Methods
3131

3232
private func fetchSnippets() {
33-
userDefaultsService.fetchRecentSnippets()
33+
userDefaultsService.fetchSnippets(fetchingType: .recent)
3434
.receive(on: DispatchQueue.main)
3535
.sink { completion in
3636
switch completion {

SnippetsLibrary/Services/UserDefaults/UserDefaultsService.swift

+25-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import Combine
1010

1111
protocol UserDefaultsService {
1212
func saveRecentSnippet(_ snippet: Snippet)
13-
func fetchRecentSnippets() -> AnyPublisher<[Snippet], Never>
13+
func fetchSnippets(fetchingType: SnippetsFetchingType) -> AnyPublisher<[Snippet], Never>
14+
func saveSnippetsLocally(_ snippets: [Snippet])
1415

1516
func fetchRecentSnippetsFromAppGroup() -> [Snippet]
1617
}
@@ -19,6 +20,7 @@ final class UserDefaultsServiceImpl: UserDefaultsService {
1920

2021
private enum Constants {
2122
static let recentSnippetKey = "RecentSnippet"
23+
static let localSnippetKey = "LocalSnippet"
2224
static let appGroupName = "group.com.cphlowiec.SnippetsLibrary"
2325
}
2426

@@ -46,8 +48,9 @@ final class UserDefaultsServiceImpl: UserDefaultsService {
4648
)
4749
}
4850

49-
internal func fetchRecentSnippets() -> AnyPublisher<[Snippet], Never> {
50-
let keys = userDefaults.dictionaryRepresentation().keys.filter { $0.contains(Constants.recentSnippetKey) }
51+
internal func fetchSnippets(fetchingType: SnippetsFetchingType) -> AnyPublisher<[Snippet], Never> {
52+
let fetchingKey = fetchingType == .recent ? Constants.recentSnippetKey : Constants.localSnippetKey
53+
let keys = userDefaults.dictionaryRepresentation().keys.filter { $0.contains(fetchingKey) }
5154

5255
return Future<[Snippet], Never> { [weak self] promise in
5356
var snippets = [Snippet]()
@@ -67,6 +70,25 @@ final class UserDefaultsServiceImpl: UserDefaultsService {
6770
.eraseToAnyPublisher()
6871
}
6972

73+
// MARK: - Local snippets -
74+
75+
internal func saveSnippetsLocally(_ snippets: [Snippet]) {
76+
for snippet in snippets {
77+
saveSnippetLocally(snippet)
78+
}
79+
}
80+
81+
private func saveSnippetLocally(_ snippet: Snippet) {
82+
let snippet = SnippetPlist(from: snippet)
83+
let snippetDictonary = snippet.convertedToDictonary()
84+
let snippetKey = Constants.localSnippetKey + " \(snippet.id)"
85+
86+
userDefaults.set(
87+
snippetDictonary,
88+
forKey: snippetKey
89+
)
90+
}
91+
7092
// MARK: - App Group -
7193

7294
internal func fetchRecentSnippetsFromAppGroup() -> [Snippet] {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// SnippetsFetchingType.swift
3+
// SnippetsLibrary
4+
//
5+
// Created by Krzysztof Łowiec on 12/10/2021.
6+
//
7+
8+
import Foundation
9+
10+
enum SnippetsFetchingType {
11+
case recent
12+
case local
13+
}

0 commit comments

Comments
 (0)