diff --git a/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift b/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift index 2c8fa72f8d..4643240757 100644 --- a/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift +++ b/iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift @@ -284,7 +284,8 @@ extension BMCViewController: BMCPermissionsCellDelegate { }) case .backup: viewModel.grant(permission: permission) - case .restore: assertionFailure() + case .restore: + viewModel.requestRestoring() } } } diff --git a/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCDefaultViewModel.swift b/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCDefaultViewModel.swift index 91c0cafdc9..71afe25e92 100644 --- a/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCDefaultViewModel.swift +++ b/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCDefaultViewModel.swift @@ -17,6 +17,7 @@ final class BMCDefaultViewModel: NSObject { private(set) var isPendingPermission = false private var isAuthenticated = false + private var filesPrepared = false; private var onPreparedToShareCategory: BMCViewModel.onPreparedToShareHandler? @@ -38,7 +39,7 @@ final class BMCDefaultViewModel: NSObject { isPendingPermission = false permissions = [.backup] } else { - isPendingPermission = true + isPendingPermission = false permissions = [.restore(BM.lastSynchronizationDate())] } } @@ -222,9 +223,103 @@ extension BMCDefaultViewModel: BMCViewModel { func areNotificationsEnabled() -> Bool { return BM.areNotificationsEnabled() } + + func requestRestoring() { + BM.requestRestoring() + } + + func applyRestoring() { + BM.applyRestoring() + } + + func cancelRestoring() { + if filesPrepared { + return + } + + BM.cancelRestoring() + } } extension BMCDefaultViewModel: MWMBookmarksObserver { + func onRestoringStarted() { + filesPrepared = false + MWMAlertViewController.activeAlert().presentSpinnerAlert(withTitle: L("bookmarks_restore_process")) + { [weak self] in self?.cancelRestoring() } + } + + func onRestoringFilesPrepared() { + filesPrepared = true + } + + func onRestoringFinished(_ result: MWMSynchronizationResult) { + MWMAlertViewController.activeAlert().closeAlert() { [weak self] in + switch result { + case .networkError: fallthrough + case .authError: + MWMAlertViewController.activeAlert().presentDefaultAlert(withTitle: L("error_server_title"), + message: L("error_server_message"), + rightButtonTitle: L("try_again"), + leftButtonTitle: L("cancel")) { + [weak self] in + self?.requestRestoring() + } + + case .diskError: + MWMAlertViewController.activeAlert().presentInternalErrorAlert() + + case .userInterrupted: break + case .success: + guard let s = self else { return } + s.setCategories() + s.view.update(sections: [.categories]) + } + } + } + + func onRestoringRequest(_ result: MWMRestoringRequestResult, backupDate date: Date?) { + MWMAlertViewController.activeAlert().closeAlert() { + switch result { + case .noInternet: MWMAlertViewController.activeAlert().presentNoConnectionAlert() + + case .backupExists: + guard let date = date else { + assertionFailure() + return + } + + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + let message = String(coreFormat: L("bookmarks_message_backuped_user"), + arguments: [formatter.string(from: date)]) + + MWMAlertViewController.activeAlert().presentDefaultAlert(withTitle: L("bookmarks_restore_title"), + message: message, + rightButtonTitle: L("restore"), + leftButtonTitle: L("cancel")) + { [weak self] in + MWMAlertViewController.activeAlert().presentSpinnerAlert(withTitle: L("bookmarks_restore_process")) { + [weak self] in + self?.cancelRestoring() + } + self?.applyRestoring() + } + + case .noBackup: + MWMAlertViewController.activeAlert().presentDefaultAlert(withTitle: L("bookmarks_restore_empty_title"), + message: L("bookmarks_restore_empty_message"), + rightButtonTitle: L("ok"), + leftButtonTitle: nil, + rightButtonAction: nil) + + case .notEnoughDiskSpace: MWMAlertViewController.activeAlert().presentNotEnoughSpaceAlert() + + case .requestError: assertionFailure() + } + } + } + func onBookmarksLoadFinished() { loadData() convertAllKMLIfNeeded() diff --git a/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCViewModel.swift b/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCViewModel.swift index 71393c530e..5ce74bd1d0 100644 --- a/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCViewModel.swift +++ b/iphone/Maps/Bookmarks/Categories/BMCViewModel/BMCViewModel.swift @@ -48,4 +48,8 @@ protocol BMCViewModel: AnyObject { func setNotificationsEnabled(_ enabled: Bool) func areNotificationsEnabled() -> Bool + + func requestRestoring() + func applyRestoring() + func cancelRestoring() } diff --git a/iphone/Maps/Bookmarks/Categories/Permissions/BMCPermissionsCell.swift b/iphone/Maps/Bookmarks/Categories/Permissions/BMCPermissionsCell.swift index 1d2e7a1f07..c286d1d36f 100644 --- a/iphone/Maps/Bookmarks/Categories/Permissions/BMCPermissionsCell.swift +++ b/iphone/Maps/Bookmarks/Categories/Permissions/BMCPermissionsCell.swift @@ -30,7 +30,18 @@ final class BMCPermissionsCell: MWMTableViewCell { case .backup: label.text = L("bookmarks_message_authorized_user") button.setTitle(L("bookmarks_backup").uppercased(), for: .normal) - case .restore: assertionFailure() + case let .restore(date): + if let date = date { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + label.text = String(coreFormat: L("bookmarks_message_backuped_user"), + arguments: [formatter.string(from: date)]) + } else { + label.text = L("bookmarks_message_unbackuped_user") + } + + button.setTitle(L("bookmarks_restore"), for: .normal) } } } diff --git a/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.h b/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.h index bb2ffd89f8..b542e90bc7 100644 --- a/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.h +++ b/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.h @@ -54,6 +54,13 @@ - (void)presentSpinnerAlertWithTitle:(nonnull NSString *)title cancel:(nullable MWMVoidBlock)cancel; - (void)presentBookmarkConversionErrorAlert; +- (void)presentDefaultAlertWithTitle:(nonnull NSString *)title + message:(nullable NSString *)message + rightButtonTitle:(nonnull NSString *)rightButtonTitle + leftButtonTitle:(nullable NSString *)leftButtonTitle + rightButtonAction:(nullable MWMVoidBlock)action; + + - (void)closeAlert:(nullable MWMVoidBlock)completion; - (nonnull instancetype)init __attribute__((unavailable("call -initWithViewController: instead!"))); diff --git a/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.mm b/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.mm index bfecbf1fda..104dd59a4a 100644 --- a/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.mm +++ b/iphone/Maps/Classes/CustomAlert/AlertController/MWMAlertViewController.mm @@ -248,6 +248,19 @@ static NSString * const kAlertControllerNibIdentifier = @"MWMAlertViewController [self displayAlert:[MWMAlert bookmarkConversionErrorAlert]]; } +- (void)presentDefaultAlertWithTitle:(nonnull NSString *)title + message:(nullable NSString *)message + rightButtonTitle:(nonnull NSString *)rightButtonTitle + leftButtonTitle:(nullable NSString *)leftButtonTitle + rightButtonAction:(nullable MWMVoidBlock)action +{ + [self displayAlert:[MWMAlert defaultAlertWithTitle:title + message:message + rightButtonTitle:rightButtonTitle + leftButtonTitle:leftButtonTitle + rightButtonAction:action]]; +} + - (void)displayAlert:(MWMAlert *)alert { // TODO(igrechuhin): Remove this check on location manager refactoring. diff --git a/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.h b/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.h index 3536ec01d4..cf6f4ff972 100644 --- a/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.h +++ b/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.h @@ -43,6 +43,12 @@ + (MWMAlert *)spinnerAlertWithTitle:(NSString *)title cancel:(MWMVoidBlock)cancel; + (MWMAlert *)bookmarkConversionErrorAlert; ++ (MWMAlert *)defaultAlertWithTitle:(NSString *)title + message:(NSString *)message + rightButtonTitle:(NSString *)rightButtonTitle + leftButtonTitle:(NSString *)leftButtonTitle + rightButtonAction:(MWMVoidBlock)action; + - (void)close:(MWMVoidBlock)completion; - (void)setNeedsCloseAlertAfterEnterBackground; diff --git a/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.mm b/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.mm index 94cfe89820..ff3e0fcbd6 100644 --- a/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.mm +++ b/iphone/Maps/Classes/CustomAlert/BaseAlert/MWMAlert.mm @@ -185,6 +185,20 @@ return [MWMDefaultAlert bookmarkConversionErrorAlert]; } ++ (MWMAlert *)defaultAlertWithTitle:(NSString *)title + message:(NSString *)message + rightButtonTitle:(NSString *)rightButtonTitle + leftButtonTitle:(NSString *)leftButtonTitle + rightButtonAction:(MWMVoidBlock)action +{ + return [MWMDefaultAlert defaultAlertWithTitle:title + message:message + rightButtonTitle:rightButtonTitle + leftButtonTitle:leftButtonTitle + rightButtonAction:action + statisticsEvent:nil]; +} + - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { // Should override this method if you want custom relayout after rotation. diff --git a/iphone/Maps/Classes/CustomAlert/DefaultAlert/MWMDefaultAlert.h b/iphone/Maps/Classes/CustomAlert/DefaultAlert/MWMDefaultAlert.h index 6151fb5a6b..ab7b86cdca 100644 --- a/iphone/Maps/Classes/CustomAlert/DefaultAlert/MWMDefaultAlert.h +++ b/iphone/Maps/Classes/CustomAlert/DefaultAlert/MWMDefaultAlert.h @@ -40,4 +40,11 @@ + (instancetype)convertBookmarksWithCount:(NSUInteger)count okBlock:(MWMVoidBlock)okBlock; + (instancetype)bookmarkConversionErrorAlert; ++ (instancetype)defaultAlertWithTitle:(nonnull NSString *)title + message:(nullable NSString *)message + rightButtonTitle:(nonnull NSString *)rightButtonTitle + leftButtonTitle:(nullable NSString *)leftButtonTitle + rightButtonAction:(nullable MWMVoidBlock)action + statisticsEvent:(nullable NSString *)statisticsEvent; + @end diff --git a/iphone/Maps/Common/Statistics/StatisticsStrings.h b/iphone/Maps/Common/Statistics/StatisticsStrings.h index 19770fa80c..09e9160a82 100644 --- a/iphone/Maps/Common/Statistics/StatisticsStrings.h +++ b/iphone/Maps/Common/Statistics/StatisticsStrings.h @@ -40,6 +40,8 @@ static NSString * const kStatBooking = @"Booking.com"; static NSString * const kStatBookmarkCreated = @"Bookmark. Bookmark created"; static NSString * const kStatBookmarks = @"Bookmarks"; static NSString * const kStatBookmarksAuthRequestError = @"Bookmarks_SyncProposal_error"; +static NSString * const kStatBookmarksRestoreProposalCancel = @"Bookmarks_RestoreProposal_cancel"; +static NSString * const kStatBookmarksRestoreProposalClick = @"Bookmarks_RestoreProposal_click"; static NSString * const kStatBookmarksAuthRequestSuccess = @"Bookmarks_SyncProposal_enabled"; static NSString * const kStatBookmarksRestoreProposalError = @"Bookmarks_RestoreProposal_error"; static NSString * const kStatBookmarksRestoreProposalSuccess = @"Bookmarks_RestoreProposal_success"; diff --git a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.h b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.h index 0460560cbc..be3f09bc01 100644 --- a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.h +++ b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.h @@ -40,6 +40,10 @@ + (void)setNotificationsEnabled:(BOOL)enabled; + (BOOL)areNotificationsEnabled; ++ (void)requestRestoring; ++ (void)applyRestoring; ++ (void)cancelRestoring; + - (instancetype)init __attribute__((unavailable("call +manager instead"))); - (instancetype)copy __attribute__((unavailable("call +manager instead"))); - (instancetype)copyWithZone:(NSZone *)zone __attribute__((unavailable("call +manager instead"))); diff --git a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm index e592f92554..d35940a95c 100644 --- a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm +++ b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm @@ -6,6 +6,8 @@ #include "coding/internal/file_data.hpp" +#include "base/stl_helpers.hpp" + #include namespace @@ -132,7 +134,16 @@ NSString * const CloudErrorToString(Cloud::SynchronizationResult result) auto onSynchronizationStarted = [](Cloud::SynchronizationType type) { if (type == Cloud::SynchronizationType::Backup) + { [Statistics logEvent:kStatBookmarksSyncStarted]; + } + else + { + [[MWMBookmarksManager manager] loopObservers:^(Observer observer) { + if ([observer respondsToSelector:@selector(onRestoringStarted)]) + [observer onRestoringStarted]; + }]; + } }; auto onSynchronizationFinished = [](Cloud::SynchronizationType type, Cloud::SynchronizationResult result, @@ -143,21 +154,33 @@ NSString * const CloudErrorToString(Cloud::SynchronizationResult result) [Statistics logEvent:type == Cloud::SynchronizationType::Backup ? kStatBookmarksSyncSuccess : kStatBookmarksRestoreProposalSuccess]; } - else + else if (auto const error = CloudErrorToString(result)) { - NSString * const errorType = CloudErrorToString(result); - if (errorType != nil) - { - [Statistics logEvent:type == Cloud::SynchronizationType::Backup ? kStatBookmarksSyncError : - kStatBookmarksRestoreProposalError - withParameters:@{kStatType: errorType, kStatError: @(errorStr.c_str())}]; - } + [Statistics logEvent:type == Cloud::SynchronizationType::Backup ? kStatBookmarksSyncError : + kStatBookmarksRestoreProposalError + withParameters:@{kStatType: error, kStatError: @(errorStr.c_str())}]; } + + if (type != Cloud::SynchronizationType::Restore) + return; + + [[MWMBookmarksManager manager] loopObservers:^(Observer observer) { + if ([observer respondsToSelector:@selector(onRestoringFinished:)]) + [observer onRestoringFinished:static_cast(my::Key(result))]; + }]; }; auto onRestoreRequested = [](Cloud::RestoringRequestResult result, uint64_t backupTimestampInMs) { - if (result == Cloud::RestoringRequestResult::NoBackup) + auto const res = static_cast(my::Key(result)); + NSDate * date = nil; + + if (result == Cloud::RestoringRequestResult::BackupExists) + { + auto const interval = static_cast(backupTimestampInMs / 1000.); + date = [NSDate dateWithTimeIntervalSince1970:interval]; + } + else if (result == Cloud::RestoringRequestResult::NoBackup) { [Statistics logEvent:kStatBookmarksRestoreProposalError withParameters:@{kStatType: kStatNoBackup, kStatError: @("")}]; @@ -167,11 +190,19 @@ NSString * const CloudErrorToString(Cloud::SynchronizationResult result) [Statistics logEvent:kStatBookmarksRestoreProposalError withParameters:@{kStatType: kStatDisk, kStatError: @("Not enough disk space")}]; } + + [[MWMBookmarksManager manager] loopObservers:^(Observer observer) { + if ([observer respondsToSelector:@selector(onRestoringRequest:backupDate:)]) + [observer onRestoringRequest:res backupDate:date]; + }]; }; - auto onRestoredFilesPrepared = []() + auto onRestoredFilesPrepared = [] { - //TODO: On this callback we have to block cancel button in restore dialog if such button exists. + [[MWMBookmarksManager manager] loopObservers:^(Observer observer) { + if ([observer respondsToSelector:@selector(onRestoringFilesPrepared)]) + [observer onRestoringFilesPrepared]; + }]; }; bm.SetCloudHandlers(std::move(onSynchronizationStarted), std::move(onSynchronizationFinished), @@ -333,6 +364,47 @@ NSString * const CloudErrorToString(Cloud::SynchronizationResult result) GetFramework().GetBookmarkManager().SetCloudEnabled(enabled); } ++ (void)requestRestoring +{ + auto const status = Platform::ConnectionStatus(); + auto statusStr = [](Platform::EConnectionType type) -> NSString * { + switch (type) + { + case Platform::EConnectionType::CONNECTION_NONE: + return kStatOffline; + case Platform::EConnectionType::CONNECTION_WWAN: + return kStatMobile; + case Platform::EConnectionType::CONNECTION_WIFI: + return kStatWifi; + } + } (status); + + [Statistics logEvent:kStatBookmarksRestoreProposalClick + withParameters:@{kStatNetwork : statusStr}]; + + if (status == Platform::EConnectionType::CONNECTION_NONE) + { + [self.manager loopObservers:^(Observer observer) { + if ([observer respondsToSelector:@selector(onRestoringRequest:backupDate:)]) + [observer onRestoringRequest:MWMRestoringRequestResultNoInternet backupDate:nil]; + }]; + return; + } + + GetFramework().GetBookmarkManager().RequestCloudRestoring(); +} + ++ (void)applyRestoring +{ + GetFramework().GetBookmarkManager().ApplyCloudRestoring(); +} + ++ (void)cancelRestoring +{ + [Statistics logEvent:kStatBookmarksRestoreProposalCancel]; + GetFramework().GetBookmarkManager().CancelCloudRestoring(); +} + - (void)loopObservers:(TLoopBlock)block { for (Observer observer in self.observers) diff --git a/iphone/Maps/Core/Bookmarks/MWMBookmarksObserver.h b/iphone/Maps/Core/Bookmarks/MWMBookmarksObserver.h index 1b2a7ae6a2..14dd178518 100644 --- a/iphone/Maps/Core/Bookmarks/MWMBookmarksObserver.h +++ b/iphone/Maps/Core/Bookmarks/MWMBookmarksObserver.h @@ -1,10 +1,32 @@ #import "MWMTypes.h" +typedef NS_ENUM(NSUInteger, MWMRestoringRequestResult) +{ + MWMRestoringRequestResultBackupExists, + MWMRestoringRequestResultNoBackup, + MWMRestoringRequestResultNotEnoughDiskSpace, + MWMRestoringRequestResultNoInternet, + MWMRestoringRequestResultRequestError +}; + +typedef NS_ENUM(NSUInteger, MWMSynchronizationResult) +{ + MWMSynchronizationResultSuccess, + MWMSynchronizationResultAuthError, + MWMSynchronizationResultNetworkError, + MWMSynchronizationResultDiskError, + MWMSynchronizationResultUserInterrupted +}; + @protocol MWMBookmarksObserver - (void)onConversionFinish:(BOOL)success; @optional +- (void)onRestoringRequest:(MWMRestoringRequestResult)result backupDate:(NSDate * _Nullable)date; +- (void)onRestoringFinished:(MWMSynchronizationResult)result; +- (void)onRestoringStarted; +- (void)onRestoringFilesPrepared; - (void)onBookmarksLoadFinished; - (void)onBookmarksFileLoadSuccess; - (void)onBookmarksCategoryDeleted:(MWMMarkGroupID)groupId;