@@ -5,12 +5,23 @@ import ShortAddressService
55
66@MainActor
77final class SportsGroundsMapViewModel : NSObject , ObservableObject {
8+ /// Дата предыдущего ручного обновления справочника площадок
9+ ///
10+ /// - При обновлении справочника вручную необходимо обновить тут дату
11+ /// - Неудобно, зато спасаемся от постоянных ошибок 500 на сервере
12+ private let previousManualUpdateDateString = " 2023-01-12T00:00:00 "
13+ /// Менеджер локации
814 private let manager = CLLocationManager ( )
915 private let urlOpener : URLOpener = URLOpenerImp ( )
16+ /// Держит обновление фильтра площадок
1017 private var filterCancellable : AnyCancellable ?
18+ /// Держит обновление ошибки определения геолокации
1119 private var locationErrorCancellable : AnyCancellable ?
20+ /// Идентификатор страны пользователя
1221 private var userCountryID = Int . zero
22+ /// Идентификатор города пользователя
1323 private var userCityID = Int . zero
24+ /// Дефолтный список площадок, загруженный из `json`-файла
1425 private var defaultList = [ SportsGround] ( )
1526 @Published private( set) var isLoading = false
1627 @Published private( set) var errorMessage = " "
@@ -43,6 +54,7 @@ final class SportsGroundsMapViewModel: NSObject, ObservableObject {
4354 }
4455 }
4556
57+ /// Заполняем/обновляем дефолтный список площадок
4658 func makeGrounds( refresh: Bool , with defaults: DefaultsProtocol ) async {
4759 if isLoading || !defaultList. isEmpty, !refresh { return }
4860 if defaultList. isEmpty {
@@ -52,36 +64,38 @@ final class SportsGroundsMapViewModel: NSObject, ObservableObject {
5264 }
5365 isLoading. toggle ( )
5466 do {
55- defaultList = try await APIService ( with: defaults, needAuth: false ) . getAllSportsGrounds ( )
67+ let updatedGrounds = try await APIService ( with: defaults, needAuth: false ) . getUpdatedSportsGrounds (
68+ from: previousManualUpdateDateString
69+ )
70+ updateDefaultList ( with: updatedGrounds)
71+ applyFilter ( with: defaults. mainUserInfo)
5672 } catch {
57- fillDefaultList ( )
5873 errorMessage = ErrorFilterService . message ( from: error)
5974 }
60- applyFilter ( with: defaults. mainUserInfo)
6175 isLoading. toggle ( )
6276 }
6377
78+ /// Проверяем недавние обновления списка площадок
79+ ///
80+ /// Запрашиваем обновление за прошедшие 5 минут
6481 func checkForRecentUpdates( with defaults: DefaultsProtocol ) async {
6582 if isLoading { return }
6683 isLoading. toggle ( )
6784 do {
6885 let updatedGrounds = try await APIService ( with: defaults, needAuth: false ) . getUpdatedSportsGrounds (
69- from: DateFormatterService . halfMinuteAgoDateString
86+ from: DateFormatterService . fiveMinutesAgoDateString
7087 )
71- updatedGrounds. forEach { ground in
72- if !defaultList. contains ( ground) {
73- defaultList. append ( ground)
74- } else if let index = sportsGrounds. firstIndex ( where: { $0. id == ground. id } ) {
75- defaultList [ index] = ground
76- }
77- }
88+ updateDefaultList ( with: updatedGrounds)
7889 applyFilter ( with: defaults. mainUserInfo)
7990 } catch {
8091 errorMessage = ErrorFilterService . message ( from: error)
8192 }
8293 isLoading. toggle ( )
8394 }
8495
96+ /// Удаляет площадку с указанным идентификатором из списка
97+ ///
98+ /// Используется при ручном удалении площадки с детального экрана площадки
8599 func deleteSportsGroundFromList( with groundID: Int ) {
86100 sportsGrounds. removeAll ( where: { $0. id == groundID } )
87101 needUpdateAnnotations. toggle ( )
@@ -102,18 +116,22 @@ final class SportsGroundsMapViewModel: NSObject, ObservableObject {
102116 }
103117 }
104118
119+ /// Запускаем обновление локации пользователя
105120 func onAppearAction( ) { manager. startUpdatingLocation ( ) }
106121
122+ /// Отключаем обновление локации пользователя
107123 func onDisappearAction( ) { manager. stopUpdatingLocation ( ) }
108124
109125 func clearErrorMessage( ) { errorMessage = " " }
110126}
111127
112128extension SportsGroundsMapViewModel {
129+ /// `true` - регион пользователя установлен, `false` - не установлен
113130 var isRegionSet : Bool {
114131 region. center. latitude != . zero && region. center. longitude != . zero
115132 }
116133
134+ /// `true` - прячем карту, `false` - не прячем
117135 var shouldHideMap : Bool {
118136 !isRegionSet && ignoreUserLocation
119137 }
@@ -179,6 +197,7 @@ private extension SportsGroundsMapViewModel {
179197 applyFilter ( userInfo? . countryID, userInfo? . cityID)
180198 }
181199
200+ /// Применяем фильтры к `defaultList` и выводим итоговый список в `sportsGrounds`
182201 func applyFilter( _ countryID: Int ? , _ cityID: Int ? ) {
183202 DispatchQueue . global ( qos: . utility) . async { [ weak self] in
184203 guard let self else { return }
@@ -207,6 +226,7 @@ private extension SportsGroundsMapViewModel {
207226 }
208227 }
209228
229+ /// Заполняем дефолтный список площадок контентом из `json`-файла
210230 func fillDefaultList( ) {
211231 do {
212232 let oldGrounds = try Bundle . main. decodeJson (
@@ -220,6 +240,17 @@ private extension SportsGroundsMapViewModel {
220240 }
221241 }
222242
243+ /// Обновляем дефолтный список площадок
244+ func updateDefaultList( with updatedList: [ SportsGround ] ) {
245+ updatedList. forEach { ground in
246+ if let index = defaultList. firstIndex ( where: { $0. id == ground. id } ) {
247+ defaultList [ index] = ground
248+ } else {
249+ defaultList. append ( ground)
250+ }
251+ }
252+ }
253+
223254 func setupDefaultLocation( permissionDenied: Bool = false ) {
224255 ignoreUserLocation = true
225256 locationErrorMessage = permissionDenied
0 commit comments