11import Alamofire
2+ import BackgroundTasks
3+ import Combine
24import Foundation
35import SwiftUI
46import SwiftData
@@ -12,15 +14,35 @@ public class OfflineRepository: ObservableObject {
1214 @Published var isDownloading = false
1315 @Published var completionCount = 0
1416
17+ lazy var lastFetchedAt = {
18+ guard let date = UserDefaults . standard. object ( forKey: lastDownloadAtKey) as? Date else { return " " }
19+ let df = DateFormatter ( )
20+ df. dateFormat = " MM/dd/yyyy HH:mm "
21+ return df. string ( from: date)
22+ } ( )
23+ var isInMemory = false
24+
1525 private let storiesRepository = StoriesRepository . shared
1626 private let container = try ! ModelContainer ( for: StoryCollection . self, CommentCollection . self)
1727 private let downloadOrder = [ StoryType . top, . ask, . best]
28+ private let lastDownloadAtKey = " lastDownloadedAt "
1829 private var stories = [ StoryType: [ Story] ] ( )
1930 private var comments = [ Int: [ Comment] ] ( )
31+ private var cancellable : AnyCancellable ?
2032
2133 public static let shared : OfflineRepository = . init( )
2234
23- private init ( ) {
35+ init ( ) {
36+ cancellable = NetworkMonitor . shared. networkStatus
37+ . dropFirst ( )
38+ . sink { isConnected in
39+ if let isConnected = isConnected, !self . isInMemory && !isConnected {
40+ self . loadIntoMemory ( )
41+ }
42+ }
43+ }
44+
45+ public func loadIntoMemory( ) {
2446 let context = container. mainContext
2547
2648 // Fetch all cached stories.
@@ -39,13 +61,30 @@ public class OfflineRepository: ObservableObject {
3961 comments [ collection. parentId] = collection. comments
4062 }
4163 }
64+
65+ isInMemory = true
66+ }
67+
68+ public func scheduleBackgroundDownload( ) {
69+ let downloadTask = BGProcessingTaskRequest ( identifier: Constants . Download. backgroundTaskId)
70+ // Set earliestBeginDate to be 1 hr from now.
71+ downloadTask. earliestBeginDate = Date ( timeIntervalSinceNow: 3600 )
72+ downloadTask. requiresNetworkConnectivity = true
73+ downloadTask. requiresExternalPower = true
74+ do {
75+ try BGTaskScheduler . shared. submit ( downloadTask)
76+ } catch {
77+ debugPrint ( " Unable to submit task: \( error. localizedDescription) " )
78+ }
4279 }
4380
4481 // MARK: - Story related.
4582
4683 public func downloadAllStories( ) async -> Void {
4784 isDownloading = true
4885
86+ UserDefaults . standard. set ( Date . now, forKey: lastDownloadAtKey)
87+
4988 let context = container. mainContext
5089 var completedStoryId = Set < Int > ( )
5190
@@ -93,29 +132,11 @@ public class OfflineRepository: ObservableObject {
93132 }
94133
95134 public func fetchAllStories( from storyType: StoryType ) -> [ Story ] {
96- return stories [ storyType] ?? [ Story] ( )
97- }
98-
99- public func fetchStoryIds( from storyType: StoryType ) async -> [ Int ] {
100- return [ Int] ( )
101- }
102-
103- public func fetchStoryIds( from storyType: String ) async -> [ Int ] {
104- return [ Int] ( )
105- }
106-
107- public func fetchStory( _ id: Int ) async -> Story ? {
108- return nil
109- // let context = container.mainContext
110- // var descriptor = FetchDescriptor<StoryCollection>(
111- // predicate: #Predicate { $0.id == id }
112- // )
113- // descriptor.fetchLimit = 1
114- // if let results = try? context.fetch(descriptor) {
115- // return results.first?.story
116- // } else {
117- // return nil
118- // }
135+ guard let stories = stories [ storyType] else { return [ Story] ( ) }
136+ let storiesWithCommentsDownloaded = stories. filter { story in
137+ comments [ story. id] . isNotNullOrEmpty
138+ }
139+ return storiesWithCommentsDownloaded
119140 }
120141
121142 // MARK: - Comment related.
0 commit comments