[go: nahoru, domu]

blob: 852eb77c78814dddd0a097ed2e8c32c9892eb3d6 [file] [log] [blame]
Quentin Pubertd00f9072023-09-06 15:10:391// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/save_to_photos/save_to_photos_mediator.h"
6
7#import <UIKit/UIKit.h>
8
9#import "base/ios/block_types.h"
Justin Cohenf053ffc2024-01-24 19:09:1210#import "base/memory/raw_ptr.h"
Quentin Pubert109160d2023-09-21 12:48:2411#import "base/metrics/histogram_functions.h"
12#import "base/metrics/histogram_macros.h"
Quentin Pubert8443c252024-02-13 16:19:1013#import "base/metrics/user_metrics.h"
14#import "base/metrics/user_metrics_action.h"
Quentin Pubertd00f9072023-09-06 15:10:3915#import "base/strings/sys_string_conversions.h"
16#import "components/prefs/pref_service.h"
17#import "components/signin/public/base/signin_metrics.h"
18#import "components/strings/grit/components_strings.h"
Quentin Pubert8443c252024-02-13 16:19:1019#import "ios/chrome/browser/drive/model/manage_storage_url_util.h"
Quentin Pubert8606be62023-11-03 09:03:1120#import "ios/chrome/browser/photos/model/photos_metrics.h"
21#import "ios/chrome/browser/photos/model/photos_service.h"
Quentin Pubertd00f9072023-09-06 15:10:3922#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
Quentin Pubert8443c252024-02-13 16:19:1023#import "ios/chrome/browser/shared/public/commands/application_commands.h"
24#import "ios/chrome/browser/shared/public/commands/manage_storage_alert_commands.h"
25#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
Robbie Gibson64401112023-11-09 16:18:4926#import "ios/chrome/browser/signin/model/authentication_service.h"
27#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
28#import "ios/chrome/browser/signin/model/system_identity.h"
Quentin Pubertd00f9072023-09-06 15:10:3929#import "ios/chrome/browser/ui/account_picker/account_picker_configuration.h"
30#import "ios/chrome/browser/ui/save_to_photos/save_to_photos_mediator_delegate.h"
Weizhong Xia92839bc2023-12-05 16:02:3831#import "ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper.h"
Quentin Pubertd00f9072023-09-06 15:10:3932#import "ios/chrome/grit/ios_strings.h"
33#import "ui/base/l10n/l10n_util_mac.h"
34
35namespace {
36
Quentin Pubert3026e862023-11-11 14:30:4337// Maximum length of the suggested image name passed to the Photos service.
38constexpr size_t kSuggestedImageNameMaxLength = 100;
Quentin Pubertad049332023-12-22 15:23:3039NSString* const kNotEnoughStorageErrorLocalizedDescription =
40 @"The remaining storage in the user's account is not enough to perform "
41 @"this operation.";
Quentin Pubert3026e862023-11-11 14:30:4342
Quentin Pubertd00f9072023-09-06 15:10:3943NSURL* GetGooglePhotosAppURL() {
44 NSURLComponents* photosAppURLComponents = [[NSURLComponents alloc] init];
45 photosAppURLComponents.scheme = kGooglePhotosAppURLScheme;
46 return photosAppURLComponents.URL;
47}
48
49// Returns formatted size string.
50NSString* GetSizeString(NSUInteger sizeInBytes) {
51 return [NSByteCountFormatter
52 stringFromByteCount:sizeInBytes
53 countStyle:NSByteCountFormatterCountStyleFile];
54}
55
56// Helper to call -[mediator startWithImageURL:referrer:webState:]
57void StartMediatorHelper(__weak SaveToPhotosMediator* mediator,
58 const GURL& image_url,
59 const web::Referrer referrer,
60 base::WeakPtr<web::WebState> web_state) {
61 [mediator startWithImageURL:image_url
62 referrer:referrer
63 webState:web_state.get()];
64}
65
66} // namespace
67
Quentin Pubertf6f63c42023-10-20 09:22:3568NSString* const kGooglePhotosAppProductIdentifier = @"962194608";
69
Quentin Pubert41cfd1f2023-12-06 09:08:3270NSString* const kGooglePhotosStoreKitProviderToken = @"9008";
71
Quentin Pubertf6f63c42023-10-20 09:22:3572NSString* const kGooglePhotosStoreKitCampaignToken = @"chrome-x-photos";
73
74NSString* const kGooglePhotosRecentlyAddedURLString =
75 @"https://photos.google.com/search/_tra_?obfsgid=";
76
77NSString* const kGooglePhotosAppURLScheme = @"googlephotos";
78
Quentin Pubert5ee1d892024-01-04 16:09:3379@interface SaveToPhotosMediator ()
80
81// Identity used to perform an upload. Should be set when the user selects an
82// identity, right before starting to upload. If the upload fails, should be
83// reset to nil.
84@property(nonatomic, strong) id<SystemIdentity> identity;
85
86@end
87
Quentin Pubertd00f9072023-09-06 15:10:3988@implementation SaveToPhotosMediator {
Justin Cohenf053ffc2024-01-24 19:09:1289 raw_ptr<PhotosService> _photosService;
90 raw_ptr<PrefService> _prefService;
91 raw_ptr<ChromeAccountManagerService> _accountManagerService;
92 raw_ptr<signin::IdentityManager> _identityManager;
Quentin Pubert8443c252024-02-13 16:19:1093 id<ManageStorageAlertCommands> _manageStorageAlertHandler;
94 id<ApplicationCommands> _applicationHandler;
Quentin Pubertd00f9072023-09-06 15:10:3995 NSString* _imageName;
96 NSData* _imageData;
Quentin Pubertd00f9072023-09-06 15:10:3997 BOOL _userTappedSuccessSnackbarButton;
Quentin Pubert7de50a22023-09-29 14:57:3698 base::TimeTicks _uploadStart;
Quentin Pubert93e82fc2023-10-04 14:30:4799 BOOL _successSnackbarAppeared;
100 BOOL _successSnackbarDisappeared;
101 BOOL _uploadCompletedSuccessfully;
Quentin Pubertd00f9072023-09-06 15:10:39102}
103
104#pragma mark - Initialization
105
Quentin Pubert8443c252024-02-13 16:19:10106- (instancetype)initWithPhotosService:(PhotosService*)photosService
107 prefService:(PrefService*)prefService
108 accountManagerService:
109 (ChromeAccountManagerService*)accountManagerService
110 identityManager:(signin::IdentityManager*)identityManager
111 manageStorageAlertHandler:
112 (id<ManageStorageAlertCommands>)manageStorageAlertHandler
113 applicationHandler:
114 (id<ApplicationCommands>)applicationHandler {
Quentin Pubertd00f9072023-09-06 15:10:39115 self = [super init];
116 if (self) {
Quentin Pubert8443c252024-02-13 16:19:10117 CHECK(photosService);
118 CHECK(prefService);
119 CHECK(accountManagerService);
120 CHECK(identityManager);
121 CHECK(manageStorageAlertHandler);
122 CHECK(applicationHandler);
Quentin Pubertd00f9072023-09-06 15:10:39123 _photosService = photosService;
124 _prefService = prefService;
125 _accountManagerService = accountManagerService;
126 _identityManager = identityManager;
Quentin Pubert8443c252024-02-13 16:19:10127 _manageStorageAlertHandler = manageStorageAlertHandler;
128 _applicationHandler = applicationHandler;
Quentin Pubertd00f9072023-09-06 15:10:39129 }
130 return self;
131}
132
133#pragma mark - Public
134
135- (void)startWithImageURL:(const GURL&)imageURL
136 referrer:(const web::Referrer&)referrer
137 webState:(web::WebState*)webState {
138 // If the web state does not exist anymore (which can happen when the user
139 // tries again), hide Save to Photos.
140 if (!webState) {
Quentin Pubert109160d2023-09-21 12:48:24141 base::UmaHistogramEnumeration(
142 kSaveToPhotosActionsHistogram,
143 SaveToPhotosActions::kFailureWebStateDestroyed);
Quentin Pubertd00f9072023-09-06 15:10:39144 [self.delegate hideSaveToPhotos];
145 return;
146 }
147
148 // If the image cannot be fetched, the user can "Try Again" from here. It
149 // makes sense to let the user try again if the image cannot be fetched
150 // because of a connection issue.
151 __weak __typeof(self) weakSelf = self;
152 ProceduralBlock tryAgainBlock = base::CallbackToBlock(
153 base::BindOnce(&StartMediatorHelper, weakSelf, imageURL, referrer,
154 webState->GetWeakPtr()));
155
156 _imageName = base::SysUTF8ToNSString(imageURL.ExtractFileName());
157
158 ImageFetchTabHelper* imageFetcher =
159 ImageFetchTabHelper::FromWebState(webState);
160 CHECK(imageFetcher);
161 imageFetcher->GetImageData(imageURL, referrer, ^(NSData* imageData) {
162 if (imageData) {
163 [weakSelf continueSaveImageWithData:imageData];
164 } else {
165 [weakSelf showTryAgainOrCancelAlertWithTryAgainBlock:tryAgainBlock];
166 }
167 });
168}
169
170- (void)accountPickerDidSelectIdentity:(id<SystemIdentity>)identity
171 askEveryTime:(BOOL)askEveryTime {
Quentin Pubert9f8784f2023-09-11 13:21:11172 CHECK(identity);
Quentin Pubert109160d2023-09-21 12:48:24173 base::UmaHistogramEnumeration(
174 kSaveToPhotosAccountPickerActionsHistogram,
175 SaveToPhotosAccountPickerActions::kSelectedIdentity);
Quentin Pubertd00f9072023-09-06 15:10:39176
Quentin Pubert457115e2023-10-18 17:04:50177 // Memorize the account that was picked and whether to ask which account to
178 // use every time.
179 _prefService->SetString(prefs::kIosSaveToPhotosDefaultGaiaId,
180 base::SysNSStringToUTF8(identity.gaiaID));
181 _prefService->SetBoolean(prefs::kIosSaveToPhotosSkipAccountPicker,
182 !askEveryTime);
Quentin Pubertd00f9072023-09-06 15:10:39183
184 _identity = identity;
Quentin Pubert2285829e2023-11-16 17:49:29185
186 [self.delegate startValidationSpinnerForAccountPicker];
Quentin Puberte04385892024-03-11 10:23:25187 [self.delegate hideAccountPicker];
Quentin Pubert2285829e2023-11-16 17:49:29188 [self tryUploadImage];
Quentin Pubert9f8784f2023-09-11 13:21:11189}
190
191- (void)accountPickerDidCancel {
Quentin Pubert2285829e2023-11-16 17:49:29192 if (!_identity) {
193 base::UmaHistogramEnumeration(kSaveToPhotosAccountPickerActionsHistogram,
194 SaveToPhotosAccountPickerActions::kCancelled);
195 [self.delegate hideAccountPicker];
Quentin Pubert2285829e2023-11-16 17:49:29196 return;
197 }
198
199 // If `_identity` is not nil while the account picker is presented, that means
200 // the user has already tapped "Save" for a given identity.
201 _photosService->CancelUpload();
Quentin Pubert2285829e2023-11-16 17:49:29202 _identity = nil;
Quentin Pubert9f8784f2023-09-11 13:21:11203}
204
205- (void)accountPickerWasHidden {
206 if (!_identity) {
Quentin Pubert109160d2023-09-21 12:48:24207 base::UmaHistogramEnumeration(
208 kSaveToPhotosActionsHistogram,
209 SaveToPhotosActions::kFailureUserCancelledWithAccountPicker);
Quentin Pubert9f8784f2023-09-11 13:21:11210 [self.delegate hideSaveToPhotos];
Quentin Pubert9f8784f2023-09-11 13:21:11211 }
Quentin Pubertd00f9072023-09-06 15:10:39212}
213
Quentin Pubert9f8784f2023-09-11 13:21:11214- (void)storeKitWantsToHide {
Quentin Pubert109160d2023-09-21 12:48:24215 BOOL photosAppInstalled =
216 [UIApplication.sharedApplication canOpenURL:GetGooglePhotosAppURL()];
217 base::UmaHistogramEnumeration(
218 kSaveToPhotosActionsHistogram,
219 photosAppInstalled
220 ? SaveToPhotosActions::kSuccessAndOpenStoreKitAndAppInstalled
221 : SaveToPhotosActions::kSuccessAndOpenStoreKitAndAppNotInstalled);
Quentin Pubertd00f9072023-09-06 15:10:39222 [self.delegate hideSaveToPhotos];
223}
224
225- (void)disconnect {
226 self.delegate = nil;
227 _photosService = nullptr;
228 _prefService = nullptr;
229 _accountManagerService = nullptr;
230 _identityManager = nullptr;
231 _imageName = nil;
232 _imageData = nil;
233 _identity = nil;
234}
235
Quentin Pubert8443c252024-02-13 16:19:10236- (void)showManageStorageForIdentity:(id<SystemIdentity>)identity {
237 base::RecordAction(
238 base::UserMetricsAction("MobileSaveToPhotosManageStorage"));
239 // The uploading identity's user email is used to switch to the uploading
240 // account before loading the "Manage Storage" web page.
241 GURL manageStorageURL = GenerateManageDriveStorageUrl(
242 base::SysNSStringToUTF8(identity.userEmail));
243 OpenNewTabCommand* newTabCommand =
244 [OpenNewTabCommand commandWithURLFromChrome:manageStorageURL];
245 [_applicationHandler openURLInNewTab:newTabCommand];
Quentin Puberte04385892024-03-11 10:23:25246 base::UmaHistogramEnumeration(
247 kSaveToPhotosActionsHistogram,
248 SaveToPhotosActions::kFailureOutOfStorageDidManageStorage);
249 [self.delegate hideSaveToPhotos];
250}
251
252- (void)manageStorageAlertDidCancel {
253 base::UmaHistogramEnumeration(
254 kSaveToPhotosActionsHistogram,
255 SaveToPhotosActions::kFailureOutOfStorageDidNotManageStorage);
256 [self.delegate hideSaveToPhotos];
Quentin Pubert8443c252024-02-13 16:19:10257}
258
Quentin Pubertd00f9072023-09-06 15:10:39259#pragma mark - Private
260
261// Resume the process of saving the image once the data has been fetched.
262- (void)continueSaveImageWithData:(NSData*)imageData {
263 _imageData = imageData;
264
265 // Although it is unlikely, the user could sign-out while the image data is
266 // being fetched. Exit now if that happened.
267 if (!_identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
Quentin Pubert109160d2023-09-21 12:48:24268 base::UmaHistogramEnumeration(kSaveToPhotosActionsHistogram,
269 SaveToPhotosActions::kFailureUserSignedOut);
Quentin Pubertd00f9072023-09-06 15:10:39270 [self.delegate hideSaveToPhotos];
271 return;
272 }
273
274 const std::string defaultGaiaId =
275 _prefService->GetString(prefs::kIosSaveToPhotosDefaultGaiaId);
276 id<SystemIdentity> defaultIdentity =
277 _accountManagerService->GetIdentityWithGaiaID(defaultGaiaId);
Quentin Pubert457115e2023-10-18 17:04:50278 bool skipAccountPicker =
279 _prefService->GetBoolean(prefs::kIosSaveToPhotosSkipAccountPicker);
Quentin Pubertd00f9072023-09-06 15:10:39280
281 // If the user has already selected a default account to save images to
Quentin Pubert457115e2023-10-18 17:04:50282 // Photos and opted to skip the account picker, use that default.
283 if (skipAccountPicker && defaultIdentity) {
Quentin Pubertd00f9072023-09-06 15:10:39284 _identity = defaultIdentity;
Quentin Pubert109160d2023-09-21 12:48:24285 base::UmaHistogramEnumeration(kSaveToPhotosAccountPickerActionsHistogram,
286 SaveToPhotosAccountPickerActions::kSkipped);
Quentin Pubertd00f9072023-09-06 15:10:39287 [self tryUploadImage];
288 return;
289 }
290
291 // If the memorized account is not found on the device, unmemorize it.
Quentin Pubert457115e2023-10-18 17:04:50292 if (skipAccountPicker) {
Quentin Pubertd00f9072023-09-06 15:10:39293 _prefService->ClearPref(prefs::kIosSaveToPhotosDefaultGaiaId);
Quentin Pubert457115e2023-10-18 17:04:50294 _prefService->ClearPref(prefs::kIosSaveToPhotosSkipAccountPicker);
Quentin Pubertd00f9072023-09-06 15:10:39295 }
296
297 // If no default account can be used, present the account picker instead.
298 AccountPickerConfiguration* configuration =
299 [[AccountPickerConfiguration alloc] init];
300 configuration.titleText =
301 l10n_util::GetNSString(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_TITLE);
302 NSString* imageSize = GetSizeString(_imageData.length);
303 configuration.bodyText =
304 l10n_util::GetNSStringF(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_BODY,
305 base::SysNSStringToUTF16(_imageName),
306 base::SysNSStringToUTF16(imageSize));
307 configuration.submitButtonTitle =
308 l10n_util::GetNSString(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_SUBMIT);
309 configuration.askEveryTimeSwitchLabelText = l10n_util::GetNSString(
310 IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_ASK_EVERY_TIME);
Quentin Pubert457115e2023-10-18 17:04:50311 [self.delegate showAccountPickerWithConfiguration:configuration
312 selectedIdentity:defaultIdentity];
Quentin Pubertd00f9072023-09-06 15:10:39313}
314
315// Once the destination account is known, tries to upload the image using the
316// Photos service.
317- (void)tryUploadImage {
318 __weak __typeof(self) weakSelf = self;
319
Quentin Pubert93e82fc2023-10-04 14:30:47320 // Reset part of the state in case this is not the first attempt.
321 _userTappedSuccessSnackbarButton = NO;
322 _successSnackbarAppeared = NO;
323 _successSnackbarDisappeared = NO;
324 _uploadCompletedSuccessfully = NO;
325
Quentin Pubertd00f9072023-09-06 15:10:39326 // If the Photos service is unavailable (maybe busy in a separate window),
327 // present an alert.
328 if (!_photosService->IsAvailable()) {
329 [self showTryAgainOrCancelAlertWithTryAgainBlock:^{
330 [weakSelf tryUploadImage];
331 }];
332 return;
333 }
334
Quentin Pubert2285829e2023-11-16 17:49:29335 // Else start uploading the image.
Quentin Pubertd00f9072023-09-06 15:10:39336 auto uploadCompletionCallback =
337 base::BindOnce(^(PhotosService::UploadResult result) {
338 [weakSelf photosServiceFinishedUploadWithResult:result];
339 });
Quentin Pubert7de50a22023-09-29 14:57:36340 _uploadStart = base::TimeTicks::Now();
Quentin Pubert93e82fc2023-10-04 14:30:47341 auto uploadProgressCallback =
342 base::BindRepeating(^(const PhotosService::UploadProgress& progress) {
343 [weakSelf photosServiceReportedUploadProgress:progress];
344 });
Quentin Pubert3026e862023-11-11 14:30:43345 NSString* suggestedImageName =
346 _imageName.length > kSuggestedImageNameMaxLength ? nil : _imageName;
347 _photosService->UploadImage(suggestedImageName, _imageData, _identity,
Quentin Pubert93e82fc2023-10-04 14:30:47348 std::move(uploadProgressCallback),
Quentin Pubertd00f9072023-09-06 15:10:39349 std::move(uploadCompletionCallback));
Quentin Pubertd00f9072023-09-06 15:10:39350}
351
Quentin Pubert7faace12024-01-23 10:50:53352// After upload failed with `failureIdentity`, this can be called to retry an
353// upload with the same identity.
354- (void)retryUploadImageWithIdentity:(id<SystemIdentity>)failureIdentity {
355 self.identity = failureIdentity;
Quentin Pubert7faace12024-01-23 10:50:53356 [self tryUploadImage];
357}
358
Quentin Pubertd00f9072023-09-06 15:10:39359// Called when the Photos service reports upload completion.
360- (void)photosServiceFinishedUploadWithResult:
Quentin Pubertad049332023-12-22 15:23:30361 (PhotosService::UploadResult)result {
Quentin Pubert93e82fc2023-10-04 14:30:47362 if (!result.successful) {
Quentin Pubert5ee1d892024-01-04 16:09:33363 // `_identity` is used to determine whether an upload is ongoing or was
364 // successful. If the upload failed, `_identity` should be reset.
365 id<SystemIdentity> failureIdentity = _identity;
366 _identity = nil;
Quentin Pubert85446c02023-12-13 14:36:35367 base::UmaHistogramTimes(kSaveToPhotosUploadFailureLatencyHistogram,
Quentin Pubert7de50a22023-09-29 14:57:36368 base::TimeTicks::Now() - _uploadStart);
Quentin Pubertad049332023-12-22 15:23:30369 // TODO(crbug.com/1513891): Emit the failure type as-is once the service is
370 // able to identify out-of-storage errors by itself.
371 if (result.failure_type == PhotosServiceUploadFailureType::kUploadPhoto2 &&
372 [result.error.localizedDescription
373 isEqualToString:kNotEnoughStorageErrorLocalizedDescription]) {
374 result.failure_type =
375 PhotosServiceUploadFailureType::kUploadPhoto2NotEnoughStorage;
376 }
Quentin Pubert85446c02023-12-13 14:36:35377 base::UmaHistogramEnumeration(kSaveToPhotosUploadFailureTypeHistogram,
378 result.failure_type);
Quentin Pubert8443c252024-02-13 16:19:10379 // If the user is out of storage, offer to manage their storage.
380 if (result.failure_type ==
381 PhotosServiceUploadFailureType::kUploadPhoto2NotEnoughStorage) {
382 [_manageStorageAlertHandler
383 showManageStorageAlertForIdentity:failureIdentity];
384 return;
385 }
386 // Otherwise let the user "Try Again" with the same account.
Quentin Puberte04385892024-03-11 10:23:25387 __weak __typeof(self) weakSelf = self;
Quentin Pubertd00f9072023-09-06 15:10:39388 [self showTryAgainOrCancelAlertWithTryAgainBlock:^{
Quentin Pubert7faace12024-01-23 10:50:53389 [weakSelf retryUploadImageWithIdentity:failureIdentity];
Quentin Pubertd00f9072023-09-06 15:10:39390 }];
Quentin Pubert93e82fc2023-10-04 14:30:47391 return;
392 }
393
Quentin Pubert85446c02023-12-13 14:36:35394 base::UmaHistogramTimes(kSaveToPhotosUploadSuccessLatencyHistogram,
Quentin Pubert93e82fc2023-10-04 14:30:47395 base::TimeTicks::Now() - _uploadStart);
396 _uploadCompletedSuccessfully = YES;
397
398 if (!_successSnackbarAppeared) {
399 // If the success snackbar did not appear for some reason (no progress has
400 // been reported), show it now.
401 [self showSnackbarWithSuccessMessageAndOpenButton];
402 return;
403 }
404
405 if (!_successSnackbarDisappeared) {
406 // If the success snackbar has not disappeared, wait until it does to
407 // maybe open the photo and then finish.
408 return;
409 }
410
411 if (_userTappedSuccessSnackbarButton) {
412 [self openPhotosAppOrShowInStoreKit];
413 return;
414 }
415
416 base::UmaHistogramEnumeration(kSaveToPhotosActionsHistogram,
417 SaveToPhotosActions::kSuccess);
418 [self.delegate hideSaveToPhotos];
419}
420
421- (void)photosServiceReportedUploadProgress:
422 (const PhotosService::UploadProgress&)progress {
Quentin Puberte04385892024-03-11 10:23:25423 // If all of the image data has been uploaded, show success early.
Quentin Pubert93e82fc2023-10-04 14:30:47424 if (progress.total_bytes_sent == progress.total_bytes_expected_to_send) {
425 [self showSnackbarWithSuccessMessageAndOpenButton];
Quentin Pubertd00f9072023-09-06 15:10:39426 }
427}
428
429// Shows an alert with a "Try Again" button (calls `tryAgain`) and a "Cancel"
430// button.
431- (void)showTryAgainOrCancelAlertWithTryAgainBlock:(ProceduralBlock)tryAgain {
432 NSString* title = l10n_util::GetNSString(
433 IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_TITLE);
434 NSString* imageSize = GetSizeString(_imageData.length);
435 NSString* message = l10n_util::GetNSStringF(
436 IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_MESSAGE,
437 base::SysNSStringToUTF16(_imageName),
438 base::SysNSStringToUTF16(imageSize));
439 NSString* cancelTitle = l10n_util::GetNSString(IDS_CANCEL);
440 NSString* tryAgainTitle = l10n_util::GetNSString(
441 IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_TRY_AGAIN);
442 __weak __typeof(self.delegate) weakDelegate = self.delegate;
Quentin Pubert109160d2023-09-21 12:48:24443 [self.delegate
444 showTryAgainOrCancelAlertWithTitle:title
445 message:message
446 tryAgainTitle:tryAgainTitle
447 tryAgainAction:tryAgain
448 cancelTitle:cancelTitle
449 cancelAction:^{
450 base::UmaHistogramEnumeration(
451 kSaveToPhotosActionsHistogram,
452 SaveToPhotosActions::
Quentin Puberte04385892024-03-11 10:23:25453 kFailureUserCancelledWithTryAgainAlert);
Quentin Pubert109160d2023-09-21 12:48:24454 [weakDelegate hideSaveToPhotos];
455 }];
Quentin Pubertd00f9072023-09-06 15:10:39456}
457
Quentin Pubertd00f9072023-09-06 15:10:39458// Shows a snackbar to let the user know the Photos service is done uploading
459// the image with a button to either open the Photos app if it is installed or
460// show the Photos app in the AppStore otherwise.
461- (void)showSnackbarWithSuccessMessageAndOpenButton {
Quentin Pubert93e82fc2023-10-04 14:30:47462 _successSnackbarAppeared = YES;
Quentin Pubertd00f9072023-09-06 15:10:39463 NSString* message = l10n_util::GetNSStringF(
464 IDS_IOS_SAVE_TO_PHOTOS_SNACKBAR_IMAGE_SAVED_MESSAGE,
465 base::SysNSStringToUTF16(_identity.userEmail));
466 NSString* buttonText = l10n_util::GetNSString(
467 IDS_IOS_SAVE_TO_PHOTOS_SNACKBAR_IMAGE_SAVED_OPEN_BUTTON);
468 __weak __typeof(self) weakSelf = self;
469 [self.delegate showSnackbarWithMessage:message
470 buttonText:buttonText
471 messageAction:^{
472 __strong __typeof(weakSelf) strongSelf = weakSelf;
473 if (!strongSelf) {
474 return;
475 }
476 strongSelf->_userTappedSuccessSnackbarButton = YES;
477 }
478 completionAction:^(BOOL userTriggered) {
479 [weakSelf successSnackbarDisappearedUserTriggered:userTriggered];
480 }];
481}
482
483// Called when the snackbar shown upon completion disappears. `userTriggered` is
484// YES if the snackbar has been dismissed by the user.
485- (void)successSnackbarDisappearedUserTriggered:(BOOL)userTriggered {
Quentin Pubert93e82fc2023-10-04 14:30:47486 _successSnackbarDisappeared = YES;
487 if (!_uploadCompletedSuccessfully) {
Quentin Pubert2285829e2023-11-16 17:49:29488 // If the upload has not completed, wait until it does to finish. The
489 // success snackbar can disappear before the upload has completed because it
490 // is presented early.
Quentin Pubert93e82fc2023-10-04 14:30:47491 return;
492 }
493
Quentin Pubert2285829e2023-11-16 17:49:29494 // If the upload completed and the user tapped "Open", open the photo.
Quentin Pubertd00f9072023-09-06 15:10:39495 if (_userTappedSuccessSnackbarButton) {
496 [self openPhotosAppOrShowInStoreKit];
497 return;
498 }
Quentin Pubert2285829e2023-11-16 17:49:29499
500 // If the user did not interact with the snackbar, finish.
Quentin Pubert109160d2023-09-21 12:48:24501 base::UmaHistogramEnumeration(kSaveToPhotosActionsHistogram,
502 SaveToPhotosActions::kSuccess);
Quentin Pubertd00f9072023-09-06 15:10:39503 [self.delegate hideSaveToPhotos];
504}
505
506// Opens the Photos app if it is installed or show the Photos app in the
507// AppStore otherwise.
508- (void)openPhotosAppOrShowInStoreKit {
509 // If the Photos app is not installed, show StoreKit.
510 if (![UIApplication.sharedApplication canOpenURL:GetGooglePhotosAppURL()]) {
511 [self.delegate
Quentin Pubertf6f63c42023-10-20 09:22:35512 showStoreKitWithProductIdentifier:kGooglePhotosAppProductIdentifier
Quentin Pubert41cfd1f2023-12-06 09:08:32513 providerToken:kGooglePhotosStoreKitProviderToken
Quentin Pubertf6f63c42023-10-20 09:22:35514 campaignToken:kGooglePhotosStoreKitCampaignToken];
Quentin Pubertd00f9072023-09-06 15:10:39515 return;
516 }
517
518 // Otherwise, open the Photos app and hide Save to Photos.
519 NSString* recentlyAddedURLString = [kGooglePhotosRecentlyAddedURLString
520 stringByAppendingString:_identity.gaiaID];
521 NSURL* photosURL = [NSURL URLWithString:recentlyAddedURLString];
522 [UIApplication.sharedApplication
523 openURL:photosURL
524 options:@{UIApplicationOpenURLOptionUniversalLinksOnly : @YES}
525 completionHandler:nil];
Quentin Pubert109160d2023-09-21 12:48:24526 base::UmaHistogramEnumeration(kSaveToPhotosActionsHistogram,
527 SaveToPhotosActions::kSuccessAndOpenPhotosApp);
Quentin Pubertd00f9072023-09-06 15:10:39528 [self.delegate hideSaveToPhotos];
529}
530
531@end