From f3b2de83512a2dcdbaa80fd6aa41b7f7a2cb559c Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Mon, 1 Oct 2018 14:14:07 +0200 Subject: [PATCH 01/58] New on-boarding and permissions priming initial commit (#10537) - Set the root view controller programmatically. - Use InitialLaunchViewController after a login without a previous session. This new view controller allows to setup the app permissions after the fetchnodes request. - Use OnboardingViewController where previously LoginViewController was used. - The launch screen and privacy view are now white with a red logo. - Removed warning by updating to application:openURL:options: callback. - Some code styling --- MEGA.xcodeproj/project.pbxproj | 28 ++ iMEGA/API/Requests/MEGALoginRequestDelegate.m | 7 +- iMEGA/AppDelegate.m | 56 +++- iMEGA/Categories/MEGANode+MNZCategory.m | 15 +- iMEGA/Categories/String+MNZExtension.swift | 12 + iMEGA/Categories/UIColor+MNZCategory.h | 2 + iMEGA/Categories/UIColor+MNZCategory.m | 9 + .../splashScreenMEGALogo.pdf | Bin 4239 -> 4192 bytes .../Images.xcassets/Onboarding/Contents.json | 6 + .../photosPermission.imageset/Contents.json | 12 + .../photosPermission.pdf | Bin 0 -> 29868 bytes iMEGA/Info.plist | 2 - iMEGA/Launch/InitialLaunchViewController.h | 18 ++ iMEGA/Launch/InitialLaunchViewController.m | 76 +++++ iMEGA/Launch/Launch.storyboard | 141 ++++++++- iMEGA/Launch/LaunchScreen.xib | 6 +- iMEGA/Launch/LaunchViewController.h | 1 + iMEGA/Launch/LaunchViewController.m | 7 +- iMEGA/Links/FolderLinkViewController.m | 9 +- iMEGA/Login/LoginViewController.m | 11 +- iMEGA/Login/Main.storyboard | 29 +- iMEGA/Login/OnboardingInfoView.swift | 112 +++++++ iMEGA/Login/OnboardingViewController.swift | 287 ++++++++++++++++++ iMEGA/MEGA-Bridging-Header.h | 9 + .../Upgrade/ProductDetailViewController.m | 1 - iMEGA/Utils/DevicePermissionsHelper.h | 2 + iMEGA/Utils/DevicePermissionsHelper.m | 49 ++- iMEGA/Utils/Helper.m | 9 +- 28 files changed, 843 insertions(+), 73 deletions(-) create mode 100644 iMEGA/Categories/String+MNZExtension.swift create mode 100644 iMEGA/Images.xcassets/Onboarding/Contents.json create mode 100644 iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/photosPermission.pdf create mode 100644 iMEGA/Launch/InitialLaunchViewController.h create mode 100644 iMEGA/Launch/InitialLaunchViewController.m create mode 100644 iMEGA/Login/OnboardingInfoView.swift create mode 100644 iMEGA/Login/OnboardingViewController.swift create mode 100644 iMEGA/MEGA-Bridging-Header.h diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index 20090c31d8..d6a6fd9da5 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -251,6 +251,7 @@ 5BFA346F1FAC5850005BFC4E /* MEGAMoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */; }; 5BFA34701FAC5857005BFC4E /* MEGAMoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */; }; 5BFA34711FAC5BFB005BFC4E /* MEGARemoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA34691FAB4168005BFC4E /* MEGARemoveRequestDelegate.m */; }; + 770629A22153855600613D6E /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770629A12153855600613D6E /* OnboardingViewController.swift */; }; 7714901520F7592100769BD4 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7714901420F7592000769BD4 /* Contacts.framework */; }; 7720C5B5206A931E00F995D1 /* MEGATransfer+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 7720C5B4206A931E00F995D1 /* MEGATransfer+MNZCategory.m */; }; 7720C5B9206AB2E900F995D1 /* SF-UI-Display-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429E32020A9A2000E0DCB /* SF-UI-Display-Bold.otf */; }; @@ -292,6 +293,8 @@ 7754ED9C20B47F1000FF8B7B /* MEGAFetchNodesRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7754ED9B20B47F1000FF8B7B /* MEGAFetchNodesRequestDelegate.m */; }; 775652D7203AD2FC009E7EC8 /* ContactLinkQRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 775652D6203AD2FC009E7EC8 /* ContactLinkQRViewController.m */; }; 775D9C0B20EA0D49001BF1E8 /* ShareAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */; }; + 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */; }; + 7776472D2158D45900EC7F4C /* String+MNZExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */; }; 777E9229203EB0E200266229 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 777E9228203EB0E100266229 /* CoreImage.framework */; }; 777E922D203EE14B00266229 /* QRSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 777E922C203EE14B00266229 /* QRSettingsTableViewController.m */; }; 77892E9B20E4FFBA0020A533 /* Chat.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41EABF161DA7C5250087271C /* Chat.storyboard */; }; @@ -307,6 +310,7 @@ 77B3227B20A343A30037FA89 /* CopyrightWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 946B2D181F46E5B9009ED32C /* CopyrightWarningViewController.m */; }; 77B3227C20A343A40037FA89 /* CopyrightWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 946B2D181F46E5B9009ED32C /* CopyrightWarningViewController.m */; }; 77B3227F20A436D60037FA89 /* NSURL+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */; }; + 77B3AE0F215D0588008E889A /* InitialLaunchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */; }; 77C544FF2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 77C544FE2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m */; }; 77CAAAE0202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77CAAADF202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m */; }; 77CAAAE3202C72F8004B16ED /* MEGAPhotoBrowserPickerCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 77CAAAE2202C72F8004B16ED /* MEGAPhotoBrowserPickerCollectionViewCell.m */; }; @@ -1187,6 +1191,8 @@ 5BFA346A1FAB4169005BFC4E /* MEGARemoveRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGARemoveRequestDelegate.h; path = API/Requests/MEGARemoveRequestDelegate.h; sourceTree = ""; }; 5BFA346C1FAB6282005BFC4E /* MEGAMoveRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAMoveRequestDelegate.h; path = API/Requests/MEGAMoveRequestDelegate.h; sourceTree = ""; }; 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAMoveRequestDelegate.m; path = API/Requests/MEGAMoveRequestDelegate.m; sourceTree = ""; }; + 770629A02153855600613D6E /* MEGA-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGA-Bridging-Header.h"; sourceTree = ""; }; + 770629A12153855600613D6E /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 7714901420F7592000769BD4 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; 7720C5B3206A931E00F995D1 /* MEGATransfer+MNZCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGATransfer+MNZCategory.h"; sourceTree = ""; }; 7720C5B4206A931E00F995D1 /* MEGATransfer+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MEGATransfer+MNZCategory.m"; sourceTree = ""; }; @@ -1211,6 +1217,8 @@ 775652D6203AD2FC009E7EC8 /* ContactLinkQRViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContactLinkQRViewController.m; sourceTree = ""; }; 775D9C0920EA0D49001BF1E8 /* ShareAttachment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareAttachment.h; sourceTree = ""; }; 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareAttachment.m; sourceTree = ""; }; + 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingInfoView.swift; sourceTree = ""; }; + 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+MNZExtension.swift"; sourceTree = ""; }; 777E9228203EB0E100266229 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; 777E922B203EE14B00266229 /* QRSettingsTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QRSettingsTableViewController.h; sourceTree = ""; }; 777E922C203EE14B00266229 /* QRSettingsTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QRSettingsTableViewController.m; sourceTree = ""; }; @@ -1221,6 +1229,8 @@ 77B32278209C6F060037FA89 /* MEGAMessageRichPreviewView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MEGAMessageRichPreviewView.xib; sourceTree = ""; }; 77B3227D20A436D60037FA89 /* NSURL+MNZCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSURL+MNZCategory.h"; sourceTree = ""; }; 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MNZCategory.m"; sourceTree = ""; }; + 77B3AE0D215D0588008E889A /* InitialLaunchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InitialLaunchViewController.h; sourceTree = ""; }; + 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InitialLaunchViewController.m; sourceTree = ""; }; 77C544FD2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAPhotoBrowserAnimator.h; sourceTree = ""; }; 77C544FE2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGAPhotoBrowserAnimator.m; sourceTree = ""; }; 77CAAADE202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAPhotoBrowserPickerViewController.h; sourceTree = ""; }; @@ -1807,6 +1817,7 @@ 1373618F1A6664C300B740E8 /* main.m */, 415227411A692F8D00EC7BB6 /* Localizable.strings */, 4122C0E71B27539F001CE833 /* MEGA-PrefixHeader.pch */, + 770629A02153855600613D6E /* MEGA-Bridging-Header.h */, ); name = "Supporting Files"; sourceTree = ""; @@ -1975,6 +1986,8 @@ E8D4B88C1A69429200A3B2E2 /* ConfirmAccountViewController.m */, A8F5D6781ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.h */, A8F5D6791ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m */, + 770629A12153855600613D6E /* OnboardingViewController.swift */, + 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */, ); path = Login; sourceTree = ""; @@ -2440,6 +2453,7 @@ 77F43DD82034637300AE4F42 /* UICollectionView+MNZCategory.m */, 77B3227D20A436D60037FA89 /* NSURL+MNZCategory.h */, 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */, + 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */, ); path = Categories; sourceTree = ""; @@ -3300,6 +3314,8 @@ E8C289FE1BF65BAB00F7A034 /* Launch.storyboard */, E8C289FB1BF65B7800F7A034 /* LaunchViewController.h */, E8C289FC1BF65B7800F7A034 /* LaunchViewController.m */, + 77B3AE0D215D0588008E889A /* InitialLaunchViewController.h */, + 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */, ); path = Launch; sourceTree = ""; @@ -3427,6 +3443,7 @@ 137361891A6664C300B740E8 = { CreatedOnToolsVersion = 6.1.1; DevelopmentTeam = T9RH74Y7L9; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -3726,6 +3743,7 @@ 77519C36203707F600FDF92B /* MEGARenameRequestDelegate.m in Sources */, E8BD127F1DB76E03003DC53F /* MasterKeyViewController.m in Sources */, 41D21E691D01AEC3002E6296 /* CTAssetsNavigationController.m in Sources */, + 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */, 41D578241B84A0FB00334ED3 /* PreviewDocumentViewController.m in Sources */, 5BFA346B1FAB4169005BFC4E /* MEGARemoveRequestDelegate.m in Sources */, 7747B2D8208625B50011814E /* Reachability.m in Sources */, @@ -3855,12 +3873,14 @@ 77D9A5712023814800B48470 /* MEGAStartDownloadTransferDelegate.m in Sources */, 5BD4A0211F838606000E24E6 /* ViewFrameAccessor.m in Sources */, 417279501DB663FA001AC818 /* JSQMessagesCollectionViewCell.m in Sources */, + 77B3AE0F215D0588008E889A /* InitialLaunchViewController.m in Sources */, 5B48F8031F4C73FE001E3A29 /* AchievementsViewController.m in Sources */, 5B20F4A21EFA9629007E0A34 /* ChatAttachedNodesViewController.m in Sources */, 837A88BF20B42290007B55D1 /* CloudDriveViewController.m in Sources */, A8AA4F7D1ED433CA00E7BA23 /* MEGABaseRequestDelegate.m in Sources */, 41D21DF21D01AE89002E6296 /* NSLayoutConstraint+PureLayout.m in Sources */, A810CF3020C157BC00A6C6C5 /* FLAnimatedImageView.m in Sources */, + 770629A22153855600613D6E /* OnboardingViewController.swift in Sources */, E8F791801BC5754A00C58676 /* OpenInActivity.m in Sources */, 774FA6642090F5A20061DD85 /* MEGADialogMediaItem.m in Sources */, 417279571DB663FA001AC818 /* JSQMessagesLabel.m in Sources */, @@ -3921,6 +3941,7 @@ 836AEDE52018AD1F00868FC7 /* PasswordView.m in Sources */, 836E74C42099DC82005E2317 /* MEGAChatChangeGroupNameRequestDelegate.m in Sources */, 41D21E621D01AEC3002E6296 /* CTAssetSelectionLabel.m in Sources */, + 7776472D2158D45900EC7F4C /* String+MNZExtension.swift in Sources */, E83E102A1E1582E8002F3E2E /* ChatStatusTableViewController.m in Sources */, 942F4FD81F500A8F001FC4AC /* MEGASdk+MNZCategory.m in Sources */, E85EB30D1DF58EBA0066AC68 /* VerifyCredentialsViewController.m in Sources */, @@ -4399,6 +4420,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MEGA.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -4439,6 +4461,9 @@ PRODUCT_NAME = MEGA; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/MEGA-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "iMEGA/Vendor/SDK/bindings/ios/** iMEGA/Vendor/SDK/include/** iMEGA/Vendor/Karere/bindings/Objective-C"; VERSIONING_SYSTEM = ""; @@ -4449,6 +4474,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MEGA.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -4486,6 +4512,8 @@ PRODUCT_BUNDLE_IDENTIFIER = mega.ios; PRODUCT_NAME = MEGA; PROVISIONING_PROFILE = ""; + SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/MEGA-Bridging-Header.h"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "iMEGA/Vendor/SDK/bindings/ios/** iMEGA/Vendor/SDK/include/** iMEGA/Vendor/Karere/bindings/Objective-C"; VERSIONING_SYSTEM = ""; diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.m b/iMEGA/API/Requests/MEGALoginRequestDelegate.m index 98b72d2b3f..7632ea2cee 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.m @@ -4,7 +4,7 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" -#import "LaunchViewController.h" +#import "InitialLaunchViewController.h" #import "Helper.h" #import "MEGAStore.h" #import "NSString+MNZCategory.h" @@ -127,8 +127,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SAMKeychain setPassword:session forService:@"MEGA" account:@"sessionV3"]; [[MEGAStore shareInstance] configureMEGAStore]; - LaunchViewController *launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"LaunchViewControllerID"]; - UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; + UIWindow *window = UIApplication.sharedApplication.delegate.window; + InitialLaunchViewController *launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"InitialLaunchViewControllerID"]; + launchVC.delegate = (id)UIApplication.sharedApplication.delegate; [UIView transitionWithView:window duration:0.5 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent) animations:^{ [window setRootViewController:launchVC]; } completion:nil]; diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index c9472d1357..bb37a63667 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -13,6 +13,8 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" +#import "MEGA-Swift.h" + #import "CameraUploads.h" #import "Helper.h" #import "DevicePermissionsHelper.h" @@ -48,6 +50,7 @@ #import "CreateAccountViewController.h" #import "CustomModalAlertViewController.h" #import "DisplayMode.h" +#import "InitialLaunchViewController.h" #import "LaunchViewController.h" #import "LoginViewController.h" #import "MainTabBarController.h" @@ -74,7 +77,7 @@ #define kFirstRun @"FirstRun" -@interface AppDelegate () { +@interface AppDelegate () { BOOL isAccountFirstLogin; BOOL isFetchNodesDone; @@ -135,6 +138,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [MEGASdk setLogLevel:MEGALogLevelFatal]; #endif + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [self migrateLocalCachesLocation]; if ([launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"]) { @@ -275,7 +280,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self registerForVoIPNotifications]; [self registerForNotifications]; - [self requestCameraAndMicPermissions]; isAccountFirstLogin = NO; @@ -349,6 +353,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( }]; createAccountRequestDelegate.resumeCreateAccount = YES; [[MEGASdkManager sharedMEGASdk] resumeCreateAccountWithSessionId:sessionId delegate:createAccountRequestDelegate]; + } else { + self.window.rootViewController = [OnboardingViewController new]; } } @@ -378,6 +384,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( MEGALogDebug(@"Application did finish launching with options %@", launchOptions); + [self.window makeKeyAndVisible]; return YES; } @@ -489,8 +496,8 @@ - (void)applicationWillTerminate:(UIApplication *)application { } } -- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - MEGALogDebug(@"Application open URL %@, source application %@", url, sourceApplication); +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { + MEGALogDebug(@"Application open URL %@", url); self.link = url; [self manageLink:url]; @@ -1267,7 +1274,6 @@ - (void)showMainTabBar { if (isAccountFirstLogin) { [self registerForVoIPNotifications]; [self registerForNotifications]; - [self requestCameraAndMicPermissions]; } [self openTabBasedOnNotificationMegatype]; @@ -1277,6 +1283,22 @@ - (void)showMainTabBar { } } +- (void)showOnboarding { + OnboardingViewController *onboardingVC = [OnboardingViewController new]; + dispatch_async(dispatch_get_main_queue(), ^{ + UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; + [onboardingVC.view addSubview:overlayView]; + self.window.rootViewController = onboardingVC; + + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ + overlayView.alpha = 0; + } completion:^(BOOL finished) { + [overlayView removeFromSuperview]; + [SVProgressHUD dismiss]; + }]; + }); +} + - (void)openTabBasedOnNotificationMegatype { NSUInteger tabTag = 0; switch (self.megatype) { @@ -1331,11 +1353,6 @@ - (void)registerForNotifications { } } -- (void)requestCameraAndMicPermissions { - [DevicePermissionsHelper audioPermissionWithCompletionHandler:nil]; - [DevicePermissionsHelper videoPermissionWithCompletionHandler:nil]; -} - - (void)notificationsSettings { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { @@ -1835,6 +1852,12 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI } } +#pragma mark - InitialLaunchViewControllerDelegate + +- (void)setupFinished { + [self showMainTabBar]; +} + #pragma mark - MEGAPurchasePricingDelegate - (void)pricingsReady { @@ -2024,6 +2047,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEArgs: { if ([request type] == MEGARequestTypeLogin) { [Helper logout]; + [self showOnboarding]; } else if ([request type] == MEGARequestTypeQuerySignUpLink) { [self showLinkNotValid]; } @@ -2060,6 +2084,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (self.urlType == URLTypeCancelAccountLink) { self.urlType = URLTypeDefault; [Helper logout]; + [self showOnboarding]; UIAlertController *accountCanceledSuccessfullyAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"accountCanceledSuccessfully", @"During account cancellation (deletion)") message:nil preferredStyle:UIAlertControllerStyleAlert]; [accountCanceledSuccessfullyAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; @@ -2074,6 +2099,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [self.API_ESIDAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:self.API_ESIDAlertController animated:YES completion:nil]; [Helper logout]; + [self showOnboarding]; } } break; @@ -2197,8 +2223,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [[[NSUserDefaults alloc] initWithSuiteName:@"group.mega.ios"] setBool:YES forKey:@"IsChatEnabled"]; } } - [self showMainTabBar]; - + + if (!isAccountFirstLogin) { + [self showMainTabBar]; + } + NSUserDefaults *sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.mega.ios"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ if (![sharedUserDefaults boolForKey:@"treeCompleted"]) { @@ -2311,7 +2340,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeLogout: { [Helper logout]; - [SVProgressHUD dismiss]; + [self showOnboarding]; + [[MEGASdkManager sharedMEGASdk] mnz_setAccountDetails:nil]; if (self.messageForSuspendedAccount) { diff --git a/iMEGA/Categories/MEGANode+MNZCategory.m b/iMEGA/Categories/MEGANode+MNZCategory.m index 7eabe4c1cf..0a413868cb 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Categories/MEGANode+MNZCategory.m @@ -6,6 +6,8 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" +#import "MEGA-Swift.h" + #import "Helper.h" #import "MEGANode.h" #import "MEGAMoveRequestDelegate.h" @@ -19,7 +21,6 @@ #import "UIApplication+MNZCategory.h" #import "BrowserViewController.h" -#import "LoginViewController.h" #import "MainTabBarController.h" #import "MEGAAVViewController.h" #import "MEGANavigationController.h" @@ -335,11 +336,11 @@ - (void)mnz_fileLinkDownloadFromViewController:(UIViewController *)viewControlle [Helper setSelectedOptionOnLink:2]; //Download file from link } - LoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; + OnboardingViewController *onboardingVC = [OnboardingViewController new]; if (viewController.navigationController) { - [viewController.navigationController pushViewController:loginVC animated:YES]; + [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:loginVC]; + MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:onboardingVC]; [navigationController addCancelButton]; [viewController presentViewController:navigationController animated:YES completion:nil]; } @@ -367,11 +368,11 @@ - (void)mnz_fileLinkImportFromViewController:(UIViewController *)viewController [Helper setSelectedOptionOnLink:1]; //Import file from link } - LoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; + OnboardingViewController *onboardingVC = [OnboardingViewController new]; if (viewController.navigationController) { - [viewController.navigationController pushViewController:loginVC animated:YES]; + [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:loginVC]; + MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:onboardingVC]; [navigationController addCancelButton]; [viewController presentViewController:navigationController animated:YES completion:nil]; } diff --git a/iMEGA/Categories/String+MNZExtension.swift b/iMEGA/Categories/String+MNZExtension.swift new file mode 100644 index 0000000000..7019e2d703 --- /dev/null +++ b/iMEGA/Categories/String+MNZExtension.swift @@ -0,0 +1,12 @@ + +import Foundation + +extension String { + func localized() -> String { + return localized(withComment: nil) + } + + func localized(withComment comment: String!) -> String { + return LocalizationSystem.sharedLocal()?.localizedString(forKey: self, value: comment) ?? self + } +} diff --git a/iMEGA/Categories/UIColor+MNZCategory.h b/iMEGA/Categories/UIColor+MNZCategory.h index d77a8a5dc2..e1a20aedb3 100644 --- a/iMEGA/Categories/UIColor+MNZCategory.h +++ b/iMEGA/Categories/UIColor+MNZCategory.h @@ -23,6 +23,7 @@ typedef NS_ENUM (NSInteger, MEGAChatStatus); + (UIColor *)mnz_gray999999; + (UIColor *)mnz_grayCCCCCC; + (UIColor *)mnz_grayE2EAEA; ++ (UIColor *)mnz_grayD8D8D8; + (UIColor *)mnz_grayE3E3E3; + (UIColor *)mnz_grayEEEEEE; + (UIColor *)mnz_grayFCFCFC; @@ -34,6 +35,7 @@ typedef NS_ENUM (NSInteger, MEGAChatStatus); + (UIColor *)mnz_green00BFA5; + (UIColor *)mnz_green13E03C; + (UIColor *)mnz_green31B500; ++ (UIColor *)mnz_green899B9C; #pragma mark - Orange diff --git a/iMEGA/Categories/UIColor+MNZCategory.m b/iMEGA/Categories/UIColor+MNZCategory.m index 47fad24077..7e5fe54915 100644 --- a/iMEGA/Categories/UIColor+MNZCategory.m +++ b/iMEGA/Categories/UIColor+MNZCategory.m @@ -47,6 +47,10 @@ + (UIColor *)mnz_grayCCCCCC { return [UIColor colorWithRed:204.0/255.0 green:204.0/255.0 blue:204.0/255.0 alpha:1.0]; } ++ (UIColor *)mnz_grayD8D8D8 { + return [UIColor colorWithRed:0.85f green:0.85f blue:0.85f alpha:1.0]; +} + + (UIColor *)mnz_grayE2EAEA { return [UIColor colorWithRed:0.89f green:0.92f blue:0.92f alpha:1.0]; } @@ -85,6 +89,11 @@ + (UIColor *)mnz_green31B500 { return [UIColor colorWithRed:49.0/255.0 green:181.0/255.0 blue:0.0 alpha:1.0]; } ++ (UIColor *)mnz_green899B9C { + return [UIColor colorWithRed:0.54 green:0.61 blue:0.61 alpha:1]; +} + + #pragma mark - Orange + (UIColor *)mnz_orangeFFA500 { diff --git a/iMEGA/Images.xcassets/Logo/splashScreenMEGALogo.imageset/splashScreenMEGALogo.pdf b/iMEGA/Images.xcassets/Logo/splashScreenMEGALogo.imageset/splashScreenMEGALogo.pdf index 79a6f1ff685183a9f82993854fc64b9f405ab35b..7a72ad4f21d14a3f94a48d31ab3700329e0f3d2e 100644 GIT binary patch delta 807 zcmeBIe4sEPpguI%J5Sb-=k{-rDDjF_f3{zn7D{_8SAC;`UOkUZ8F95lY@j* zFQ01PbEPY{Dm`mxBfX((9U^1|9O07@y7DW*7cbV zj&)8xyT4wTHSLz6U_svthC@>OU2mU=FZguE;Pdh+E9NClH7rr7_o-qr*f_axt*nn> z@79jlDdC^zXq^u(y7s7zWr1_iB^xOzL;ZOZ89FnT-ecLd;RBbqr_LV1y{kO#d9iwh z_cwf=HTCg=6)L$RY#Nc*dscsA>D;;G_JxQ9CN8}hvzJRXSn>O49JXRH%E_>3IN4b~ z=hCH27t??(Zc?v`(tb5KG}jB&_}JX(*-|I9bM-PF8`tGe*49q&p4t1Ih4IYHjcm6w zdiSebXewLFFfS#xyyWZ_9pf1beT?mG7_IQ%N}N)_V5vqpf?ZrTO1{zHRD8J@gfUg>;d%Srv8|338?&1d|+;@M%Y|BP3I^jkj7`4qwQM|k6uKaLOD z_xxja?%S)vm714QTvC*pmaB?{xq7`rs13l@~ReHh)<*z5LBtU7w~qJHL7?-`?ms>*N8I`k=z(J28Ky6=E05 zt&h!1Ig}XsWS1pU$!^-k6ByqgjEmUtZn}AKT{_C z%TxBhOS7jgS>1TowqVND7oVrbZ3w#YlK*ebFQHwv6VIH#-n%Zh=IDpx4fmhFekyMJ zpE>K>qrF_Ic`3yuMX8CoAXa`-7MH1lfr0>tVPatc6Em7TkymuH6Vn@RMw7|WOD<{6oXU)a}%Rv yO9SJiWJ8N2^VB3G<0LyS8-hv{VioM{xQa^>i%KerQq#DMEsTt~R8?L5-M9e55lt5W diff --git a/iMEGA/Images.xcassets/Onboarding/Contents.json b/iMEGA/Images.xcassets/Onboarding/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/iMEGA/Images.xcassets/Onboarding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/Contents.json b/iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/Contents.json new file mode 100644 index 0000000000..a12c174288 --- /dev/null +++ b/iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "photosPermission.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/photosPermission.pdf b/iMEGA/Images.xcassets/Onboarding/photosPermission.imageset/photosPermission.pdf new file mode 100644 index 0000000000000000000000000000000000000000..002fdc830a2269e8d90bb9cbdb18d8d8dc67f897 GIT binary patch literal 29868 zcmdqJWk6NmwmwWrODNq+BemJY2I&-$2I(%zO-h&2ARtP2NT(p(Dbn5D-Cgen<(KDw z;@)$=ykGWOthwf5#+c7|M$AbmBP_}QU<4vlHZScjZDbrI^)|O6vyw5Bfptxgxw*-h zUKv;z*}frTgFKQWV-htsvo)}WT+MWB4TKH!!1@Mcyu8RZw$=tZ=Ex4v-y)=?HN;R` zPEO=ID<2(jyKmy#M~0?n55CO$y2|C0^5juyeQKcF#WlA_<{`m(O15sp?DLV6)6>(a zi9^#$$(lu~Vr9A-!<^KN4TZF&M{OwyfsxVqa%V_FB!aZr;EF|KIby16}^Nnn@sOHmFr=NG3T{e)7Yq_q>Av1!U7A|XG;!{ zuu}GbaOP%<>Al#>o=UxfIl_s|Mkni;jQz;Zozvk*;3rM}$umhtI0pL5L&)9d=7@B| zg5hPu@E+qttB%-{1V7fq#hChy^bhL9rNc$6*-*d=u8<7ebz?N~S}WuF?_M!sL*u6i zUylVA>~|du$xXR7H00h_h$mD9wiN15@P3&(OHR9bB2L1-mSFu3%~+GOB43*eO43Gg z14A@+s$Nn^I~6y8j#aK@9LIc^o2yw6Q$-b>>rJ|pDvRm%v}mYnwA+o+nHlvDYU1_` z`*py>5(cGNGnB1dSt#wPNT-)Y>S{tyBM^qhU`JU#mk!eRi>fJOb}#B$GFI5I?Hl8e zKI&gs55-^wC<#np=4|(BS959un>jFac2jV4BLcCk^q+|@qBcTOU--NK_Fg?WlL_+zP@To1T_tWtJ5~F+%rgqi0ekqnc zK@TanA0u`>Ll+8pDJ2?lp7#yW5k5hr+57|96b22V1dP^-U@kfdU`~|Lyi;J}9K?C9eL_BcxEC7y z*%Qu4q&rw7e4Rd^FfU_Z>_D$~*ps|%Fd!Vks=AJT&GPIffSkLKMFPNqt^EXzoquC) z0mNmPq=UOz&!+_QBahcjuGak zPI^rwV|dH4%)wK@u)h)b(=QA?F3ks}+Q z@sGJaYH&xbLN#daRx?GG9>S>=&(x_1W`W-bQV<@EQNYCHPNm{WS;sP{b|u)IC~-bo zpX!;ZtGhW^hxwu&et6z>7IRU@1G;gsaksKb)V$e0T^%zmU1GZ|4)aQEtYwjiH>fsC zlxUPvHyG^1h%k+l-rQqB8=QEa{T(z3z|deUL(tb{6G5`lF zWT<|h3c#=aiCKf~EPpnVj7h;-$HK-^2Qqnjj*x+rCu6zmEi#ihnXR=QWMU=A40X(G zZo&S~cpokejuS!k-Py_5#i#vLw36tWPB%Xs0BeNxK#rEyywt^=q{o8agw9LZO1XW% z+thi}$nBbbXQ}v(SUFpy`5^LSf8Qz&$*1?Ywjyk z-e57u(8Q(G%zRz;`F%xjvqD*lIh}yoldd00(;Yz-yDG-6of=+iX1>QX5)<{nA8*2S z&*|b&DjvtDcCxk;0l9^`f;lz5b`yAwh<6TOMQKjz>Er0;=d+{+u^IXVXu6d4PWi6o zVLm%8WLKLHWUWE>SB)rJL-0A5Mz7Wet>DLW|*?uSG={~)bjiyq#!!JU{diwm@b(>O4ss`b_g?#w)Sk5Znn;Faj6;(@Q|w7R)-4ra^q2R;ig@mK~S7gapJBbX*DN>r8M2k<2QL(J-|YvosS`uh{&ngX79 zRYflaE$U$dOoIwKBVko=$79f&F|&u*@%VV6;z+KQnBP%v&;S=$`qN+1A9qYEZpQh* z8%*E?KH}5|>g2Xx2&ONybG50&PYje!o@mB^KQa$fIW0_P5o*O(WLmsUDu{21CL}kA z^A7CKHzAMIPkZWM!p!o{Gi;+KbQ0)vH)_i?CY;=3Lcwi><3u%o{-){bn z%Xffr3nCy6j(Z@zTiy4-@_&S+EMzP{F$4kze?d|p8OyC|xMhmwko%vk@%FV}pm;|q z<=;TQaIXRWpakv^_P*`+pm_hee?UC{V^CycCFA6P;4wP{?>YYrioZ10e+3l3C{|gh zi=l4qC>5fGVsaAInqV70&a@<^kNaN#!|xl?{skvd``0wPetqwj_@$*?D+jbAZ?|M7 z{Xn|!US|CZV(0vcYs(pd{X4joXJ-owYYU|A*GC)CBzz5#Z9i0aQK+N8m2VEMwSf!F zA7;%`^x?@yx2<_vY;T7%o_rs7^tQ89{^*B}$?lZY4

`o*uNTY@6PhybjMn_)Iti znBU!CSZIY>tZ2Dm+5J^=NjicpBJcP|K9!|%s`_{qR~eBxQoN~}N%{a%+lhDlS_1DS zly>cVZ1fCiTN~$(3%1aBvht{Bj#v_$WcBSH;jb!fcR8P8QNAKo318(J(Z)`!+6YQJ z_{bLF+}O;LJ819ANtMyK-1Oj^twxyzTs>fQX}%^`3qV_&v{{^3r71|^Mw=U z1Z_MR2mpf~bDoJQ?6x`y==$&p(HfMevf`@<&gquQ4FX6hm=LLKl`6XcWRp z0pdl>mKsRtCWEcEoI#4Y`OxI=2p=r>4>$y9YD3Ylsp65-A$~}w*^UMXJ+^q_mK?TmEH$IEx0icb_K6?w68eI9aqNcx?y7$AHxF1c0aBR(FN>Z$HK=YMf{F_t zBm>wN8<-Dh#^a67jNVEaPE;-CIG5r9g84l;_ z`AgWPMi}Tr=6u!~j@IW(;DVEhxh;6kn!?)fB9$4|s$qB&hm6E$;qirB%!d}z#N54_ zdd$XBW(9_igyYTI>23QWez3)^?IfKS0M3;UPfS&&rsr_sHXqA@4ViHSOrO1HXvnXc zm_E)aySmZXndPtw^xLbSo+c!U4dyr!Z|z@GrelCxXL6WCKW%bOOei2?vajRXTrr** z7khCb30iar55|lsJ2;yKCom}`On)2UnL4UVb%#190!#e^T)StNAOQPsxOTfh@3G-8 zxc1MPAlcuTAS)U0j*=lWiP-?jfWPy)KQlK7e*H!me`juYn0DWgUm(Tui{Abt>-awg zDF8Esf=ev$^0zLcwm<3N zJ&bAejYDbp07D~U&h~v~XCZf}`D=`XKD30@+1cp}zP_D9;YPtZRwLI$(lzoc5&OFM z54`bf+f54xb9+1PwbxcDD?QB1>3svv{mvHs@|;^QtMU9gmm8$!YrK8TeVJ@eEETv& zKRSGQYu+CHxlg&1%k>EWJdSPAKk?bZ_NlBXk59_*8`s6jq~fvW40*B-7lU7mgR8a+a-4_vyt>O1|bHB90jwya>&s8w@_gG&eaBKv32gd7DD+>VjZv@uanD4LCKG zXz)A^dZ05{>XF>nG@H-*P_MY+F*cj;TdlqgVw=i`N zS-1ArJ(v1rU_y$1a-^T8*)M|{cncNx4f$z2O6u5{LMlUCdUoc<7GN7=TZpMAMh3iR zV)syc_u#(jFN6dB6aU|TjBsqsj4T`wcmr`TvI0PC5Jm<7FtPxdA%Mrm!pII_Wr3ic zq>in%F~pF)MQ4Z+d;9xGEJOzW!rXs~g=9>^25*h^48*K;AbtH8fN0PSKx<@16`iZe zIA!u6eEybbhu+LqS5%QQR4xg7GLgBscFCEL%B)a$o@n z7P0)2EF)I)XqI9mW#d2Rob_5ZJwOjyF-VLYxv()G-68H1RMcn?G@S2eo7WeyI>o1x zy%zOxY;jPzfI8p%v*BrJh*jPh?_&i<~I`myLMy! zqniDvV#9jx)wplf-Gje~4eL+c`rmPBu(2`%I60U>5Dw4C!omrW7G^d^b|5<|gw2B( z+1WY&9kF5kWl#JU#U{80xse~k>w>$b+9S56W9s^l{cZZ!2n?C;SxD#C`H1YVfiAGX zV%_EdRho*gPaeKpH+-`zNH>I?h3D_k`mQ-#KlD*64FJ6%?NK1khB~d$2|sR}rI34E znXwzU*2;qkw%5q=*n#`a`acpQ3bx(aXFgh(tfJ@Xx`$*e5VAj|TKl`$+ztF+#OAK+ z_Y?FNZ2Biowm*vv8yV|8Ui`swW4$Fx_n-dfV)Jv^{z2mYD9pdmhV{Sc3F8DZGP46A zP|Cr~$ic?W387XXRz@Iz9paf|2QhN6aNLrte+4(J|CZP6KZ2X!qFcBTK5=Wg;-4Co zigPg1Nqr2=rt=(x%A38}>wfD*eK8e`|2zsPUJO$zs+gVfUX98?&+@JH<2Iaca{GR4 zvJA^Oov_i_XkA%|rf6^5u%%x`9w`r>tT<0tatl6I5}(HwMf+f2j^`~-!Ht*mDQ$kg z&QO^v-G(Dh6JUQ0JmP?P=_^OWW^s*vg_%|;P!4#_18)s!dZH`XDs50GpVPd%r6M(JtZ6NUkRsiGe$L=m503(2f8A6jF zGbju;*D!V^T8KhlIufkRfAwVPI_Z#+D2KAA<5OMpv#}gT%Hh&AXZ}mbygo1)caN^M5X?0@34h zSZr+Ujm7n|87%O4yCpLyKwH>pPu?wzAo;>kFSwO9BQAot1($yZ*HA$_a*4ykr+V#S;k?!Oks2$>7^kUycGk#Va z-v(7LgZLE`uHfL&J|->Q(fq4Z7~VJm9vklbVX-H6M4fWeJ)mi7YA*K*2Z1=mWL%(y z5AkrF(^<9)Pi)7j|G5k1!(JV6_(v*$;b5&S(&gayQ!*W=_4K&Q??W~wWM1Ki4mBG@BaM(=#P;T1l@(MXNuH3LbG$>Z5@7c(E-j&nyp^beF_xFM4L9;a2@iH3@FVUMm{mNfpv(V zzSfT^YNJ>%R0f{}sSQ`uxVlb!>f94lw*;Ey$7DnPVd_&;QTpN3atakD-6G9(@Jd;P zM`~mfRNP1!4(_XEOr<)i$n&BvmwdGk%SfjCAob=SCkkuTC z3p-$sQCfH-TSCE${o&XNj-^B=~R0;A`7XjM?N;?hJ zMg+bW^;CSaq!y*@aeS#b?{Jx*wn+bs9X2aQ&}!sYDhQp>o;9t~ zI?dRC@I`lwTgt%G5#S1W^pH-H0$=b@Wc<3WuvfNS2`kT2A`#}NJVVb9!-Jq6!-KlX zQf%UUeVJfJm>#skuZIJ#DwsX$F>w5@*lf7tiX}+B2W!4mCt6iYOW9sq@4W+I3Njf^ zqCU^9b9Pp$P3SVX4B1$KGMWE^O&$_co>E5`tl8GSD2yNaY&~gDU3t+k%9MMA)C6xa z$(oo{`Qv$&LH&~&bSi35y*8D8fc3l^Pks+)Z58BO8(>S>L`n3y0G8r;AlHms5*)!(=9so{;JumTQgXfIHL*^tl9KX zcbhv%x_2bGj~i`8I(*Iwi6L^&$)UISr7JC1D&Aljr62pa?6C8aW3TA~eJR$k&1rp6 zWwDJ*>BA~3kxmf#H_AgrLry%a#A_j*YD6kwQsm{Lb`*s+rvhJ2qpUKufJ}07T*t0= zyP0zyqifhd_UMnf?>M%06^118x)Df;MZ& zvNMHMoJ)!zY$3e>A_@M6ka6^CVMRKz!~SX?_XxiM(4*1>=;>KU=Vck*o46>{+?F63 zDO>6BLDJ*zR3RXi?f5A6!TIQD$-UuW+$uakDUbZR;(R^!nuVU-`SkU4W$l#;vl?|% zHekVURGm!`pCcFev3T?P!1e5FH@jIG?_**5;o6tOfy%j&=NC?UmN-jy{M%wg zat&8O6P?Vskwa&Cht%i$&e3ciW~gX-L#JD1eJkSW`uFc5n#E+?CZJid!+ktyLvBd= zLEJaZkddY(MW-Paa!yexvw68|PFFj(A9Y%FE(|kAgd{_eV|LI_b25kftPlMxu^Rji z6?S@i5Of4>ZLCv3i^8_TZSDTVSBFNJfP_=J=@XLWX{GJB)t#CI-t&iSf?&g_6P})O z{NM1wdc`}T$*F=lRbNE%55Njb|k{@<-xjaTR2za1As|KhrTQaPcjbkInF(s`XUdT3$8+XoSf|mW5 z8W{+}yMrZ@6q`BFu!9{9oaf%~w4RO+M!b!TR-OxH=ZNlNo0DXY=3?7<*!9eD9iJQ1 zOo>o#hE#%xTdvKJpHvfOD@aaFP|G0VJ?6<;-AGfQw%}oMuqTJ3@lcVa-|nD2s!LV@ zZ&Lvr7h|i}swll@i^_SW$b|vEmYFDx_s_)$2Id%uS_j3AHh;OaIbXm0?C$PzBU`({ z!JW-iya6NpS~f3rX%%zsR>ZQVlMD3WqL@7_!1zA#nTZzNyLkY{3Pl8PB+1w21To+` zp2h-aHEQY!>p~AHUq|Fl>99~ytI7uD`PoxHY&+%sMxl_Gx_57_QV@x}kEx*`~$k9=>xeavON3$*F?LN_{4Qj}QmoBchHw`p$eHR)y8>eN@iyo3uVvh~;K*TtO={~Zp9u~!H++hJUs6G3$0R5~ zuhqEs=O#;m+WB}0{ehRt&qE|UXhq#}<1a!EZlLioR;d0?`5?ykPqUH*$oiYDdFRXg z3+Ds=w2S_q?&&{~ct~ErZyX-ti2lE$d^Mr|uP2#NMIe+p)(%M54rIjgHdY+{u2n$#xF|W3?CvaEE(t# z=;E|N=-)f~6apK|P&b~TZ=f9~Cd9S!(3i*!T@M)0q^M&fijoemou>9Go43-7^5Z?h zW(<;FC0K&fRh%P%iac`M=BiB0H_9-wn!wrdW|q$>1Fh6-0?I;*L5G@1&6%pk&&(o@ zXARA@B0!>v72FbK^*FG!S;4I2+nbl0+}kW&fE`)RW!IMXl7Za|Z}6$Ih1GbV=SQyb zZByFMBfax6(}gl^ri_W7D9|L<_+~W9K9|Bhn!kLS5Bg%dbCT{Z8 zWp%14E{B24M$?=Rj5Jj)Uf{0qgR~L=%j#1H#aBx$OSX)RBhJl=<0m>sEl zBp7r~4Up8Hu75L~BT$Pn%e)l5IXGTl-_Q0I1s+u_hwnD#kmL^KycLy5!>D5|bu5*x zX4+JB_U4bzV|ItDBto9XMJy63C)<%^F`HVrnznVdBev&rtDwEy;1^afmn}$L!tX(U z2OdJjNPJMLxV3>|T9+dq!r6cjf^Y`T2hWqlRXp;sBTkYbey1XM^qHaa>f=CTrXm!P zU0S=k!@PZGK|(?TFGOx~8)tovCR#b|j%TK)C-YC>K*XS~^(@f-W;qO1(toNlewLVCx8<(YR8%xTW~$ ze6D3ShUb+kE3$}KYbW5X71|S;1?1gcPRmA?k+Mtu6U!S6`-&%ej&JQ+kOJ#2%KOZS zR%*mFbu63C%@^Xh4*7VwTemKvv*NR!5AaBd>A-JaAj?yfXLr}nXofzth)PXv|j@olOp$L!Q zJ6>UL^AtxciG}VR9G$IkZaeh6khkgSknsnpE~igc1sh>sK^u-Xyh zT3L2~`F>h+X1=!7e8?NWqqszSaIB7aooa{2BZ13zJ8+s%J(lu^uhwU)E=xW4rB`?Y zXi8BBHSRlk_B-GmD`J%s@~(i#N%G#6qt05KyG%lurFI`+2dsC#@fY(U*Iujz#K0C; z6w;HOF!_a?gp{MKN(k+rF#D<7a8>VSBbXkg5^GjdsCi(`mw_C#M!)Ut@RKp0@D`_K zoy(0Zl5E}Nxbt2&UtQNW&cMVcWthY}5i|6h;sJ%&owT>vHXIzbz?Gzz*AKKjaNhkL z`0w!i7UKax7M6SO&0RF&FTf8m-|sB)f6LehLG1fq&YRoYf9kvW-sE9MxR2UtNm6?P@O@xbce7be|>-xmd7Z$_AO%WxVqN z4aSWDBlbN^`_O}c&+R71{BJ_8ZElKinbva4)Ih9@y|TPHtvRKwzUmiL>$=vR+dAcv znk(}m11CLYJSktE<=e;(Ud~j6X7P5?Oh0W4W;MwSR4-(6cgUc9gHg5?`fMBmv(< zboW`;xM@6JMmF%-?BkSRkhbdH?9~ySD{aeBMlLv0jq1Ul`jEJrqh-i08QG|wA1O29 zrL&^0p!EJ}w-nlQx^JvvuLbn2!CehRU&9jX6ORwhtZPRJ-zJa+YZ&!>LYexi{CyZE zWfk9kvCZao;#8N2*0wO` zP>74)J&vB0^aPE~=s4&L_iSE2{m?hdztJh2kEY#F)?;T;lh#tX6*qU~E>k)BPu zj&GPcZH|&2_@$c8MOaTJ&uw1h1gY{K>~1OEth~LbV31N1gl^j8oSMrv?!?}W!U8Ic z#LhK5e50&WFuUliQEVHW&}CR9K2X|O!wpp6!7r=kR>LrQtFQNS;44)Qj+ou`k{)X9 zR*|}?4SZwlbSg&sCXRPJoy#p3z_&cBboGGYvBNpCYWgNqFR4_{kkhwPj!kZUqN?r$ zYTQbLqR_ktBqA=e5~?N&Z7)tgqwCQ2EU;}sspY@!7-ISSFcn6r=?MkLz8V2{LWmOb z$hc?7Z@bU-Tl(K^ z#N{so@}IDKNR06wP8gBQaLdJz({I{ESOdyQR9v<|cOmZdknIFgpFxW4)%0Xn7|;~1osvZT*} z6tVeNp4|Ec31ZTH2+JedydR&n7o7(z;R=Cwz)Qov$4)d(ajP{gP}E@~y2-ep&7lmR z95hFUMp8!|FB&%7<-@PEXpM9Fd^;7qSPM!8@5QSGz%cGk&>rB2fYO6h~7MUTl!3)$|qjT&G@wQx)b(pNO_Fs>05Qui-fh5!nbY zRB&5o&k^+%!5sL+^^B=AaYdzvsb@*FCL*T*Rx6wBgN+~`nlRq1*`{66fzo>ov4`X% zv&~jFG*2Esp*5r*>0Ue9MeTlU(44W7VI8TQlWk?HEOw)AXimB+y6QJQ5f%yAiGhlB zwaVlWqDr(+bh3(I12^V3?)&k8OAF5}_|0r~fiF73zA~d=0YXvR+EFhb4=+!|^YE%q zdPhzCul!)z{cE~rF`l2d&8@abz4b(YP*51(Mc!Tk;sxTv)X_vyF@AWXH85 z=1~>G0b|y{Wv)BoanpiO1+js#LGVaInL5kL0EpUQ+lN7&Xva}=By#WR+OD-Q$=dU> z;gbg@S80j9=!8KMW*BI;3_){GoLCceyG|cx!4l={DBt)GUzu6oSn|dBzN1xIlzHs! zEXDAi_?&r+Zr^h;z;|6prPhoa8aubuQ)|P(OeI83k6%5EKk~LWmRZ5IRdvr!YA5|d z%S3IPOY>3tFlEJru1Zi@_(mLZSKq_8OFBg<9P-TMBx=Ta3+Y`FqP`Of$>bC5u{p}} ze=}$j5H$E|s`ns$_GX}5BcW=d${O5}Cswq^5H8zi+9K_QX~+8cJ?K+||6xcUu=pa| ze^A^d3vH`>xL`tSiDVnF-UJ5}Ar5L_Yqe>8=Qx>6YWR>8zS648Pk`R5$<6i5E{b-U zyea6psMKUm&!xN8&E-+?d4HrJ(WhTH>@b?PAa0A9GsZ@Z?|xjmHyxnK*o>E`ONSB_xVm2dmz=Pc!hp+&LQ zCHk#px^`FPDX)OZ9N(S4f?kmVGnkm5zkg#wY|~p_agyB8bmy!?yV;1{u#PMyBBu@* z`(c%J^cen|c?8@S)NMP?TFVb3s&eXC2(TgV?C=CpS!+@4G?rx_^KCrz6i)jRg9}P; zaO=ffGaqWYsdTV&RotFC2<~mW6~i~QJF5~M`DNio;w}wyB#C(~eOo#7KlJnYq!}@W+oa?F$_GixRvq5? z5CHIpGDv&EmB(G#ksNGySnMb%(^N{|P|wD5WpZh?mpW-;`q-hcmeOp_F%}2hx}^wn zbsC82fW=|+cAY7Jmx84+PDy6uX;Vm_bO(zI%*(JM^^@{-1GjvQx)}GHy_!iy)lY@l zzWmL!BeR^1WH1^NIs$an@;2iFUUKlfzPY-* zaIBcZpIT;g{h;0y73ExkQ{cmJw6Z7o3jpZ{Gki9L8n__Yav z&lTlM7qt3iHbf0Fp9_PU2`om9Y`i#Q-k1y(W<`flog8kKOcqo&aej*mIZMA)u42p*~$DMW>>=_A-CT4K;d={YaRl$Mf` z)|C=ZujUwY%yWsYMa{?eptUZcDDQ0dXUi=h)k0}UIAmrh30GV2PB7Z?i`ojM&x}2s zk*W=^>fNFnK59{?AclXk|AO?DTIjR>o^tM*5W__V$$c1$WydaN{7AWgv6+M3kUAV4 zj{}KnG~4U_Y$6>G`4!N0p~uK~#(XPNNU#Zg+dXN<`i^`2Iof=ub#FB>WKZR9*_d}R z{J*GY_Wy*e{Vjjs_iO>=KPQy^o>%ZIwg2B_C4Y)JZ>g?1O@dH6EMmIGPTZ#+k!5v+ ziF{z2DCk-hbCE>FNTL}V9vDdF!NQ~2?J0Wb=IA>hX7=0$A$NhnceUE`@@g8mvRYn= zTTrX^;hIU+^<~Jq`?&q*BE6=RV%Cb3Li3?I%5H}fge2mR5>7sdxn`;}` zWR|a*Hn2*b*+B$%6W3f{R@O41<=AOg+KD6O*{S=R#ZcNHk@9hBB=9N|lZRWl4+Ez# zjXFlddv`aV?SQLec=)tXh4N46BD?Rd&(9;&#GeGt#>+V>xr1dIW+@$n+@&81pJN?= zdDj$pm~uHWadF|x)=xu2LF(4)2k?G?Nu==}*S6tWpBzbs29LGfVagYVq!op$u&%Zd zE8l;1w^y@w=D}6RgaNWVE|Erw?*Za;gPjC&I|4^zU?U1wvr1$f2F;xOE?*w!$CyS| zYzOHZo8X<@be=EvwiZgX%-BR~r2-BEB;888AZBDv>~Y3uF5&f$(e?PX25vzrF|twFotV=Nj(Rv>Goo) zZ>x8M$hag=_|ywC0h#LsmCGl0)SZUdqm}VUTRLP~Y+J7|W0-mAxt_VEacNp@KeJuf zT|Cod*Mf=g&@H!EWV|F~m+a?W;F63ZhWf+uLr+ICm>0xJmlC^H8){N2aEnjoeXoX3 zGWurpT_YPa4&i2jE)unzmwCe+Si*4gBkLivWy)Fr85em{S9A=4(}}zuEWFxR$l7jL zZh=QHq32@Ho;c)`mmQrG*^LJ_?UZT0z?g34>v8e5-vUgh(O66Zc4`(dbwiyPF0EU( zWIA^TzQQ^bCp^CVu00`;DQvxUB{Kgf_67|1WaI7S98QQ=`E@zeOYzr^on(*rZ&6Wxs|Y> z{ILVNn{$iCv(x;mga#ZSalAp`vC#o{$n}YWdTmQ<8Ema0BJUWQM4pC&!lYPB7dqaI zWde3iIZL}p*~FPcQwq@i>q*Q3ZddX3Y{To!9=i2SCt0hy1?^`{cGrUqZiOm8W7bz2 zk{M9QPL?UDfwrKY%Dn|yQr=U2Q)#91DU4^7?A%IBwpHhIXr|buovaGZu8|JM`V;T# zxCT@TuMypOE>E>D6dEq!ULAUeHIW~fSG=3odQp$c zcl9o5*QHi)Y|a{~0rzIC5i^ocy(nQCcDiu|nSl|hA{sgmy+-15g>?SexeM*7ad-qk zxuZGPwcg2B?6i615gSHm1A=+9pbxro!S3a|#ZzTczZP8o1_E!&jfV-_D=i7>5z8un)#J?+SO$fas8L*3m+ONipYpUEoytd&CHZRc!rylWF_hI$-jejk z?ECR&NUa)IvJVx!UeNg=)AYy9N1%wngR!kQOO-qi>hV~4OYE{OvhDJoV^I#9uzOtx5~Rz{~Q>@86j3 zU6>Lw3Z{$8fWg_{c&g}_+0!5fENEK$K1+cO5?oa^7OBIkF2K zbMM)B0RSB+`iF1a2egDf`+5}gHilASWV{TrmjSNTT#PL^)3iyhW$8(8p(a1i_)EnE ztC_{a1f~+Fbb*>8(2j$Hoe}_eQY)F+ulNJ{9}V zq|ARWwg&pMH{$LFSAi3S=pRBSR-t#C~ZZC`?g_NQ(>0m+h(MMql*^$Rd(#%)X>yI*spZdQ@8TQQwiVHuo z^B+LFE!_|riKck!U)gNz`{HR>=27y-t4FSsPF4liQ_96(uh8u-W{%{xlNY|za!gl_orDywME!}d7Z!bIuUMvjm9N-QO)_m zg64At}etG-)O&$e}q*0=K7YS=^pk^NaY)i9rs&`3IT6e`56B zifVp@ig?$bINOn{x(CLG7}A7lY-y_`O0lQDl*FigDlsMaIyQtOQ2gnWe&;4d_p5#s zSGsrefjPC}0@XDPi0}HH8io-rGC97b+7jmr6 z<00>^d8l+C@mPnx6d{p@3W2TD%p=C(tj{=%9*TQh{oQCwfs$$;>vAa)+4pOGYJHtG z5AC5J<&|1laXgXo_g>&rM%^M%S)9|HulMFl-UlRB(H601-I}H9{b=dW&!y=MU+(Je ze5>5@$oE0tQ{h_9RI;8Zq&Jc~$Di z5s!@1-p>_UGZWcsba@r`q|iBhhzag$jIsN?6|a#eXix~RVP8VmB1kF=n?QGnFdDES zpNzLnF#F?XAtS!yLvSJip^i=|?#;z0{X|dt7%J^1?J?`~`gv`mQQi@I9Op`Ge-{g~ zmlHi5e&30wU`*=#2+ZlUuO0*tsO09cUsp&5)UZ>NRN z?M&8jSdn($kDD@MQKQhzzNae|{H4tLaET_x?!sn8d0wSYT^a;Ca}@>E6*$TPd+=R3 zbz#>yWkz=zl?%yy=hH}*pZ6%11UP!z7;vC5(;&=D5)}Gqk~j`Y>H#{^q-(_cD(%>Y znANU_2Xl1ibW7}fPim>La)zX1R4a6!IXBKxha>X3s*4k@FP6*o+O+^w0@@iY*d49H zn(gH`$t$C6YJF?XmAJdzeBRt>Ih?reShW!dKf~$!zCgm^W4{Eu^C;d~&^)TElyCp6 zmi+5W=M&ILZ8FWY7GIev#~ulxL*Sc(uGbrhCu(_J=(FzcSKnQeCMCKk{#~8iA<->v z0+=~C@5$fYVFI@gA*XSP3keD8*cj-O-Qk!Vna2G~9QP?%f5f$4TO0ltxE3E_uOL5$ zXNtgy(^RFEmsP9EFAbj($o%FNhG*~pmDj_XJzl+5KjxJFxLXb;w8WvuOu z2K7geJDN{m@Gzj!l%ebA3=QGLPK99xFKc+BqcdN$79Vdjdgxb8(AUXO*$m&fe|*Xz zL;(+lEHcE-?M8*W1oK(GxCNi(2JXjUI4z40fCVQRDJA#mLUV!HamMnqs`jWzs>Ngi zm2UiM)`XxcndunHxzr%IWc4V@S))`J1cMp{@ffJ36Pkg6)3{eq%vjM{ohwwyX8tT9 zSoqui`!G<5pPM!xFlL>Vuwem6YdE0#ag1w8xQBsu5j z@T%vq1T~l$*P{R&Ap%XQVjlhG<5nKg)|Sff@|&}|u^AFK`7569P8W)MRGU2s0znBX zGiIB)Of@)A#}ulu#KpDYiclry@cc`2xzamiQ2P&{N~YruNPNsR!z61ZL^Wi*+?%2L~{X89)&y!pn8)zW1l%A0;A>7%CiWb?Ue|3U!vrx}z z+|}MhAD38CD7*R@$;tulO+CYDfxo=&}RQt#L;;nm@k>K|I7pn8b$Bl|Q+Fhfs!8Y8~j#lTJx$PUFxLys05 z#voKhbQBVh0b#+m2>FL%gz=yCQf1=Nz0Xb-lHoZdwV+8v zGobTu6spu?tnfJzqF7_ctgr)i90G3r#N5kr9A31tM%+2VT_}2Qt;SD?{NE8_`$Uxy z3HO8rLzO9@F`t-ze3}N6B@z>gmPT0d;l)R*MOb*>WSuBHoL2k=UzMOa-Pbz9x-XfB zqV%F(zrtN+H^cyXp1u2`J5-deOr6X37Kt4*_5(xnYwZwiwZh0E*ioI$=R|02_4xBd zmTu*E>MRy)`EX_EBSBRj8%+=AsP-JH>87!E-k~?x?CQEasYP}tawnMyA#JUr4nj6wl}0C5HJ$_ulRRn{|8+V-grhGVn3E^Sfps6e$i5tY;@EO8$STsaEb z=n_u_ag(PSR6JBQPd>GC>5Ez8sqkw`=Tjt-X;Gqw@yUFqp~8}WNhBgTCdK)(g5Qzf zQRs`@!%T}oX`r@5vqZE+uvGj*4!)6D z`xGdL|0U>CP*qUK0qb%_&qB`xdx5d$O*L)!8Jz{4OZd}p!*J;EknC5A;32G`_-ylR z=|N@_Dia-(F_ViS#+(m589nw1J@HzsH;b3P>xGs6PUYt&M@{QI>z@c>2xQ(aPB`KHMn9=+YEU`SaU$eEm%zm>tz$D1~l!;`0% zS*&c7DplP5SR;&4jYFVJy-eO-P+EdXnMtcab|;xvHZdYN^V_Q#w)x>VKF=dB>UQmcSH-UT(PLjQR2F!2xVlJcX2(+b*^JtaM; zrps@rSaaB_+ENN9RaA0Chm$y)xWBukI>w$oL*WTj4vfc{!qKANqL*jbpeI$KRe7Ln zUb0?z-Zb7EW2&L&+U8@jpjX_TKDGQUFN)s-9!C&e@eEx8{i9f5+vki=D+E0# zntqW5N*|R9%EC@`cT9FNc)57}c|Y(zu6hpmDkAV1wD{7?Sdxm>VaZ zSm1$aMCQ%)+Z0S;u&vpl+SRJ}E-^7MpC6&x^E>?xGbNq9s$JqZMbsN!=DYpmb3NDR zpe4;s!|!24QC@Gn+HXeHYWnF*WJ}OUOc2OkFBr9dqYZ48VxERRP344P+A;4nTsv4C&3*MDp6A!!?3woqVmS^^6 z*jUUkv01)_`qS!U%%mmueA7zPwmaMi?r3jZ>9Fhby&Aq?PLbu^ldUmOBIBr8T{^Z_ zIU919<*s<&SF9*D$dF;g(to&1K1n{eQpY53@l*Yck{j}472F@xDJ<$NXe{Q3jPkO~ z2BQAaYG2dCj{A?xf(D<=j;OjRe;aC9_nNOrTTk0|9CbMEI5;$2x}p-Tr>fQ0mOZMO zHPy15vRqWAJXfittA0<1(lo>%Bp>?nO@(BRnOJxF%<~yf`jD7U6*DeH^YQ%+1HDxv zcRf1sg9z(LgqY4SmU}dA$eCwdzgwp}IQFWhbv9+Dw942++vDcE_)=J_n73G3!@Cgs z?kior$d|~ch<8Cx>R(UXYp~iGT@!XWojENBOD)+XLb0Nk#v3-77Zb<@`tJL?WlXd0 zB(4{>srjoV_YC&<6+TCpx=6c>Oh4$1KmXx8_tn@v*VVxF=$O1_W2x4#4>WPH=tM{N zxv(*=unE2gdUb|NRTf-8yLK5uDm^A4|lZE;5JnC$4!O{B1s%3eyyxSF-oz1eGosaTy$>eXq@du$xyQ!LE87x67e zwuZPB)5;~9FFGTZCDt1FEpW*B^=jQ$Y+zL9gm)F^ZsA5mduF1$<93CyUQ_K?^Y+>3 ztCGVnE74m$>vumJe3|TYG+lg=?mwJad3D`$CwW_cwIOVJPIgl^A~EQyqw9(9%T8=} zjXsTRc&%*zWl@V;hT_(*Pz2<+)9A1FRe!E&_t&t0u4(@yq5OJQ|9R9?1~?{u^jZA} zWahs;tfwiHsD$^@3+;)&ll-VC`mqt3GJ^jZuwJZOmvaEZlG-AobJwF~Br%!(HXqiY~RO8H)?KrE? z*5mY!K^49cTD*=}-s9x-Vd2xNC1Z`pU>tiqIV==SQwTaT!q9j*y1s~}lL0;-Nyq6P z&Qb9(F9<_IC&f|)brVlldVT@Z+f~-xEE9CWFTQZjdL@I@uO{)p+5%{x?*C}N4aS-X90Yj3JvL9E#7J&8+IX-spXDt*z2++3kMn7GrWH-b9Wkc`#Wu9wXfiK0-Sz$h)nQ z@gwB}g>#J92TtX*#*G(@skn>mYme~-=dXsCHF%e3} zc*FP9)V$YGAuu#j)5arCV__n4X|;I!O)9+wrPQ`zkW zHso9zV8;ySJn~FjMcI|S!sZ%f&)39_)h0nK&hd~V2=$-6Ylt4B3`5rhQdhTmrzVq| zi?Zl7;)BzosK^i(XUb?j;!QO7g8 zf6%#55q!2LHcjz8`M`__q^n#OJ!Y8VIWyxLh)}}uc0;`jo+Gm#U53e?#m;6pYm&1< z7UueG^KZj8i!VL9)e zrJy1nzYcvyxI^_$T2j|Q>Mc}`;!-tzF0}>mm1-tA68g8g*5vsQJ4Z>ZPiPRwkMev^ zSbfN@Lsd>xbJnLF%>Tk~TA%G1$jBG1zwnHg)=Na^wF)%S$52O4+9yIMQ8(fvzXewz zS!PXTUCiswkHmBI4kWYHTd%L?7NI;dN7ag{Exl!{hwdSmf*U6CmdIU&v;w2XGQYle zI;j%aaUawfeu9ba^6rZ0n(unlrPyWO<=e$dAM`>tFZEkn ziiw7rP*z-_x037?i87hlv;DWj6DHOIo<<)*}>q@|?m zJ^e@fwfMb)ai%)jIxhbT{~E~|$sS1|$tB5XN$gVu7n~zru{3>ELshj;x=$D_s`|YA z_WitN)~S_d!Dg%5e77y`5ZqR{V{j?+_IgH~LP_d!oJ_iU`_44ys_WJz=kq({JNP?r z;qbA>QSfFUD9}vw!UX>WCL<*y?UK5Eu-R*~h#Bf`t_(sme={XBd^3%LIdBxgEzlM$Mm^&h?;Dm*m3R_)`EHrfo0msdDDKse((P7{)U1w`{U=i&IijBelUx#;n(E1vJbNBDQFgR;XlHsz?^WWP>X=jM=f2hPtwulclu!#V zXIo;sBh4=`%`QCH)&uUbP9C$Yp{QZKpr1;0!LOY*6(&kp&tJb=A6?H7D0_Az)+A;# z=CL&5SsdT}mM)_=(S0rNTk=}2D++hYDMnB8bYfrB81)#PFiJBbdA|_L6QFfkz`y9?n4XwrYLW81b+c)XUt-@#Wvfa5*;A~ zcba*ExuuwRD=(JqS)qRaUAkLQ=XkK1{TjP=_O=nFh~o7!)#9|0yS8Oh*;7pOl^r|f zBz5|w4W~ksLXZjB3EvWY1wA%wzBVuSFLr;vJJ0=fcw(omp~ZJL51T#^H68nDJHo0c z5yRY@Q@?2JVe3)pk+pYW-N1pZEMdov-@Mdrt9?3$K!A{zIF9rpfiM9V0WXOu5s2vS zNoOhx+Bj-g7AJ5m$UwIdBPVI%^rgQ|o$&%ih+@Mza$7R>GY$w1kN}UX`DQD1A$7;& z$a9;4x#wn$2Ltj0&xkPv5Xi^9T$A5O%}V_&=D{o+h*fEu?JE(z|jartpZuYFKbu83Y#5qKk}Rz`tmI$;s*G4cJnX>mS+5*r)e`!t7muX--DuTdy! z?aU_aZMt%;(Be_?Was4K+|~vy^JutfaPUYnfOR z=PcD<&v!fQuFqci?#MUC*RNW#GOwJHW4TD$*R7&|N3kHbv`l9s$K}*UzQ_Eoy(TbP z7|tiU)VY*x)Gb_5|E#23G-ACsL%50G=efJpwUs$SP)^cLugW+}rqyg!Y}EslvF(fJt40RQ`qkVt zwl`-^OR_i1xS~{DZN5C6?0P#L*hx9?sc=XTw$k!G4`R%lNOsk6*LRfpLw??XY{CY5 z#JPIq#)`;BKm$*Smf4*3G-@jq8zRPAre#?^?vOT}(a;@fKHMv#u~Fu}B{~|!Y-wYu zH4`{j-V@PgzpXTrwJ`8~z|HZ^qQEw0G<~d0Y7A64UfI8sLE=McwhRBJO;V;=7P3LK zZugyWV`a#<+*j7UX?sf3sxN0%JCi9|AY;~c!F--_p=!&iXJVNB340CUR262ga31r% zbJ2ZcuR`DYlg=V+I*-!hm238%>tcW@Z@*89FH?ETef0N?@q*o1H><&s{Hj~d+a^xc zSNhA|d&unOO}TAuyD-}bxo#r4HNP_cyx zzTdn4`>d_Qb^Pg~^S`U(4?NW_#_BJ4D$f;9{7~z~iN93umxl@8@AFW=nVlmJCdi+= z?*H$q_v_`uztw~P%`M=k|IGicdjFRm`pYrp-yZts+ms((7r1~ywg7n3(7;r8KV%Kg z5dsQOWPxAcRui_e2RLZJdlo6veRkiE?L?eI1+L5B`Z_=m{)|?ZS0&`fbc{6 z5sRGcIDch|`qdRVZt_C`DjgUJfdTkp2ppvb2D1Y{zb$_m;)Wf-|HQyv9|)YbpC=F< zLl}schGPld#QA#crwNF+tPKAA|KE~2*%%s;f>}^dQh5#^U#-Sb$+VP=rN8%4?5EL4~n;g`@NEi;Ja>x#K#15z%_-OfnOpaW8 zG+&?za8T$2d4Pdld!#IIIPPS{Av-Ycwm6_cz-S!t^`HjjJJKh?a3uIhdI$*kaG8M~ zezg7I2psG7a2`j?0z({WD=-3zJemgrdAM(Y0ao=<4T@tK9>^Dr;yY4ipbctWsKadq+^a|X5CS;!awt700(GPhL6LA=gp32_18!;DSba!? zfscHb2*{B!3(N(arvSZnwb%DuB!qUiUAIp#X z`zEjk7SM+Q-=D57+5nD5!gZk#J+vVNfdK34@$tcQff#*4r2pMy-(wQsV*=Cf{-g&5 OCRi{j2qYpaO8OrYz9NbM literal 0 HcmV?d00001 diff --git a/iMEGA/Info.plist b/iMEGA/Info.plist index 3015135cab..e737bb5373 100644 --- a/iMEGA/Info.plist +++ b/iMEGA/Info.plist @@ -143,8 +143,6 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/iMEGA/Launch/InitialLaunchViewController.h b/iMEGA/Launch/InitialLaunchViewController.h new file mode 100644 index 0000000000..93ba13a8e3 --- /dev/null +++ b/iMEGA/Launch/InitialLaunchViewController.h @@ -0,0 +1,18 @@ + +#import "LaunchViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol InitialLaunchViewControllerDelegate + +- (void)setupFinished; + +@end + +@interface InitialLaunchViewController : LaunchViewController + +@property (nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m new file mode 100644 index 0000000000..bb311afdbb --- /dev/null +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -0,0 +1,76 @@ + +#import "InitialLaunchViewController.h" + +#import "MEGA-Swift.h" + +#import "MEGASdkManager.h" + +@interface InitialLaunchViewController () + +@property (weak, nonatomic) IBOutlet UILabel *titleLabel; +@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; +@property (weak, nonatomic) IBOutlet UIButton *setupButton; +@property (weak, nonatomic) IBOutlet UIButton *skipButton; + +@end + +@implementation InitialLaunchViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.titleLabel.text = AMLocalizedString(@"Setup MEGA", @"Button which triggers the initial setup"); + self.descriptionLabel.text = AMLocalizedString(@"To fully take advantage of your MEGA account we need to ask you some permissions.", @"Detailed explanation of why the user should give some permissions to MEGA"); + [self.setupButton setTitle:AMLocalizedString(@"Setup MEGA", @"Button which triggers the initial setup") forState:UIControlStateNormal]; + [self.skipButton setTitle:AMLocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; + + [[MEGASdkManager sharedMEGASdk] addMEGARequestDelegate:self]; +} + +#pragma mark - Private + +- (void)performAnimation { + [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + self.circularShapeLayer.hidden = YES; + CGFloat newXY = self.logoImageView.frame.origin.x; + self.logoImageView.frame = CGRectMake(newXY, newXY, self.logoImageView.frame.size.width, self.logoImageView.frame.size.height); + } completion:^(BOOL finished) { + self.titleLabel.hidden = self.descriptionLabel.hidden = NO; + self.setupButton.hidden = self.skipButton.hidden = NO; + }]; +} + +#pragma mark - IBActions + +- (IBAction)setupButtonPressed:(UIButton *)sender { + OnboardingViewController *setupVC = [OnboardingViewController new]; + setupVC.type = OnboardingViewControllerTypePermissions; + setupVC.completion = ^{ + [self.delegate setupFinished]; + }; + + [self presentViewController:setupVC animated:NO completion:^{ + self.titleLabel.hidden = self.descriptionLabel.hidden = YES; + self.setupButton.hidden = self.skipButton.hidden = YES; + }]; +} + +- (IBAction)skipButtonPressed:(UIButton *)sender { + [self.delegate setupFinished]; +} + +#pragma mark - MEGARequestDelegate + +- (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { + if (error.type) { + return; + } + + if (request.type == MEGARequestTypeFetchNodes) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self performAnimation]; + }); + } +} + +@end diff --git a/iMEGA/Launch/Launch.storyboard b/iMEGA/Launch/Launch.storyboard index 92ee5700f7..8891c67ba6 100644 --- a/iMEGA/Launch/Launch.storyboard +++ b/iMEGA/Launch/Launch.storyboard @@ -1,18 +1,23 @@ - + - - + + + SFUIText-Medium + SFUIText-Regular + + SFUIText-Semibold + @@ -48,7 +53,7 @@ - + @@ -101,7 +106,7 @@ - + @@ -122,8 +127,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/iMEGA/Launch/LaunchScreen.xib b/iMEGA/Launch/LaunchScreen.xib index 3a4f6126c2..a7fb494a6a 100644 --- a/iMEGA/Launch/LaunchScreen.xib +++ b/iMEGA/Launch/LaunchScreen.xib @@ -1,11 +1,11 @@ - + - + @@ -14,7 +14,7 @@ - + diff --git a/iMEGA/Launch/LaunchViewController.h b/iMEGA/Launch/LaunchViewController.h index 8f5064e8eb..502712bfb3 100644 --- a/iMEGA/Launch/LaunchViewController.h +++ b/iMEGA/Launch/LaunchViewController.h @@ -1,3 +1,4 @@ + #import @interface LaunchViewController : UIViewController diff --git a/iMEGA/Launch/LaunchViewController.m b/iMEGA/Launch/LaunchViewController.m index 1ef13e00ba..e35397d959 100644 --- a/iMEGA/Launch/LaunchViewController.m +++ b/iMEGA/Launch/LaunchViewController.m @@ -1,5 +1,8 @@ + #import "LaunchViewController.h" +#import "UIColor+MNZCategory.h" + @interface LaunchViewController () @end @@ -17,8 +20,8 @@ - (void)viewDidLoad { self.circularShapeLayer.position = CGPointMake(radiusLogoImageView, radiusLogoImageView); UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radiusLogoImageView, radiusLogoImageView) radius:(radiusLogoImageView + 4.0f) startAngle:-M_PI_2 endAngle:3*M_PI_2 clockwise:YES]; self.circularShapeLayer.path = [path CGPath]; - self.circularShapeLayer.strokeColor = [[UIColor colorWithWhite:1.0 alpha:0.70] CGColor]; - self.circularShapeLayer.fillColor = [[UIColor clearColor] CGColor]; + self.circularShapeLayer.strokeColor = UIColor.mnz_redMain.CGColor; + self.circularShapeLayer.fillColor = UIColor.clearColor.CGColor; self.circularShapeLayer.lineWidth = 2.0f; } diff --git a/iMEGA/Links/FolderLinkViewController.m b/iMEGA/Links/FolderLinkViewController.m index 9f443bd3eb..bf8ec71fcf 100644 --- a/iMEGA/Links/FolderLinkViewController.m +++ b/iMEGA/Links/FolderLinkViewController.m @@ -4,6 +4,8 @@ #import "SAMKeychain.h" #import "UIScrollView+EmptyDataSet.h" +#import "MEGA-Swift.h" + #import "DisplayMode.h" #import "Helper.h" #import "MEGANode+MNZCategory.h" @@ -17,7 +19,6 @@ #import "BrowserViewController.h" #import "CustomActionViewController.h" -#import "LoginViewController.h" #import "MainTabBarController.h" #import "MEGANavigationController.h" #import "MEGAPhotoBrowserViewController.h" @@ -516,8 +517,7 @@ - (IBAction)downloadAction:(UIBarButtonItem *)sender { } [Helper setSelectedOptionOnLink:4]; //Download folder or nodes from link - LoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; - [self.navigationController pushViewController:loginVC animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; } //TODO: Make a logout in sharedMEGASdkFolder after download the link or the selected nodes. @@ -551,8 +551,7 @@ - (IBAction)importAction:(UIBarButtonItem *)sender { } [Helper setSelectedOptionOnLink:3]; //Import folder or nodes from link - LoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; - [self.navigationController pushViewController:loginVC animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; } return; diff --git a/iMEGA/Login/LoginViewController.m b/iMEGA/Login/LoginViewController.m index 55223ab34b..d371b05f37 100644 --- a/iMEGA/Login/LoginViewController.m +++ b/iMEGA/Login/LoginViewController.m @@ -18,6 +18,8 @@ @interface LoginViewController () +@property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelBarButtonItem; + @property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *logoTopLayoutConstraint; @@ -48,6 +50,8 @@ - (void)viewDidLoad { longPressGestureRecognizer.minimumPressDuration = 5.0f; self.logoImageView.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; + self.cancelBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); + [self.emailTextField setPlaceholder:AMLocalizedString(@"emailPlaceholder", @"Email")]; self.passwordView.passwordTextField.delegate = self; self.passwordView.passwordTextField.tag = 1; @@ -67,8 +71,6 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.navigationController setNavigationBarHidden:YES animated:YES]; - [self.navigationItem setTitle:AMLocalizedString(@"login", nil)]; if (self.emailString) { @@ -131,6 +133,11 @@ - (IBAction)forgotPasswordTouchUpInside:(UIButton *)sender { [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:@"https://mega.nz/recovery"]]; } +- (IBAction)cancel:(UIBarButtonItem *)sender { + [self.view endEditing:YES]; + [self dismissViewControllerAnimated:YES completion:nil]; +} + #pragma mark - Private - (void)logoTappedFiveTimes:(UITapGestureRecognizer *)sender { diff --git a/iMEGA/Login/Main.storyboard b/iMEGA/Login/Main.storyboard index 75b46fd33e..6f52cc0ce8 100644 --- a/iMEGA/Login/Main.storyboard +++ b/iMEGA/Login/Main.storyboard @@ -41,9 +41,10 @@ - + + @@ -80,18 +81,18 @@ - + - + - + @@ -122,14 +123,14 @@ - + - + - + @@ -243,8 +244,14 @@ + + + + + + @@ -873,7 +880,7 @@ - + diff --git a/iMEGA/Login/OnboardingInfoView.swift b/iMEGA/Login/OnboardingInfoView.swift new file mode 100644 index 0000000000..6a4060100e --- /dev/null +++ b/iMEGA/Login/OnboardingInfoView.swift @@ -0,0 +1,112 @@ + +import UIKit + +enum OnboardingInfoViewType { + case encryptionInfo + case chatInfo + case contactsInfo + case cameraUploadsInfo + case photosPermission + case microphoneAndCameraPermissions + case notificationsPermission +} + +class OnboardingInfoView: UIView { + + private let imageView: UIImageView = { + let view = UIImageView.newAutoLayout() + view.contentMode = .scaleAspectFit + view.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + return view + }() + private let titleLabel: UILabel = { + let label = UILabel.newAutoLayout() + label.font = UIFont.mnz_SFUIMedium(withSize: 19) + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + return label + }() + private let descriptionLabel: UILabel = { + let label = UILabel.newAutoLayout() + label.lineBreakMode = .byWordWrapping + label.numberOfLines = 0 + label.font = UIFont.mnz_SFUIRegular(withSize: 14) + label.textAlignment = .center + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + return label + }() + + private var didSetupConstraints = false + + + + // MARK: Lifecycle + + public init(type: OnboardingInfoViewType) { + super.init(frame: CGRect.zero) + + switch type { + case .encryptionInfo: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "You hold the keys".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys") + descriptionLabel.text = "Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files.".localized(withComment: "Description shown in a page of the onboarding screens explaining the encryption paradigm") + case .chatInfo: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "Encrypted chat".localized(withComment: "Title shown in a page of the on boarding screens explaining that the chat is encrypted") + descriptionLabel.text = "Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive.".localized(withComment: "Description shown in a page of the onboarding screens explaining the chat feature") + case .contactsInfo: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "Create your Network".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate") + descriptionLabel.text = "Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA".localized(withComment: "Description shown in a page of the onboarding screens explaining contacts") + case .cameraUploadsInfo: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "Your Photos in the Cloud".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically") + descriptionLabel.text = "Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now.".localized(withComment: "Description shown in a page of the onboarding screens explaining the camera uploads feature") + case .photosPermission: + imageView.image = UIImage(named: "photosPermission") + titleLabel.text = "Allow Access to Photos".localized(withComment: "Title label that explains that the user is going to be asked for the photos permission") + descriptionLabel.text = "To share photos and videos, allow MEGA to access your photos".localized(withComment: "Detailed explanation of why the user should give permission to access to the photos") + case .microphoneAndCameraPermissions: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "Enable Microphone and Camera".localized(withComment: "Title label that explains that the user is going to be asked for the microphone and camera permission") + descriptionLabel.text = "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone".localized(withComment: "Detailed explanation of why the user should give permission to access to the camera and the microphone") + case .notificationsPermission: + imageView.image = UIImage(named: "privacy_warning_ico") + titleLabel.text = "Enable Notifications".localized(withComment: "Title label that explains that the user is going to be asked for the notifications permission") + descriptionLabel.text = "We would like to send you notifications so you receive new messages on your device instantly.".localized(withComment: "Detailed explanation of why the user should give permission to deliver notifications") + } + + self.addSubview(imageView) + self.addSubview(titleLabel) + self.addSubview(descriptionLabel) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateConstraints() { + if !didSetupConstraints { + setupConstraints() + didSetupConstraints = true + } + super.updateConstraints() + } + + + + // MARK: Autolayout + + private func setupConstraints() { + imageView.autoPinEdge(toSuperviewEdge: .top) + imageView.autoAlignAxis(toSuperviewAxis: .vertical) + + titleLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: 28) + titleLabel.autoAlignAxis(toSuperviewAxis: .vertical) + + descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 28) + descriptionLabel.autoAlignAxis(toSuperviewAxis: .vertical) + descriptionLabel.autoPinEdge(.left, to: .left, of: self, withOffset: 35) + descriptionLabel.autoPinEdge(toSuperviewEdge: .bottom) + } + +} diff --git a/iMEGA/Login/OnboardingViewController.swift b/iMEGA/Login/OnboardingViewController.swift new file mode 100644 index 0000000000..3c7745e0a7 --- /dev/null +++ b/iMEGA/Login/OnboardingViewController.swift @@ -0,0 +1,287 @@ + +import UIKit + +@objc enum OnboardingViewControllerType: Int { + case onboarding + case permissions +} + +class OnboardingViewController: UIViewController, UIScrollViewDelegate { + + @objc var type = OnboardingViewControllerType.onboarding + @objc var completion: (() -> Void)? + + private let scrollView: UIScrollView = { + let view = UIScrollView.newAutoLayout() + view.isPagingEnabled = true + view.showsHorizontalScrollIndicator = false + return view + }() + private let pageControl: UIPageControl = { + let control = UIPageControl.newAutoLayout() + control.pageIndicatorTintColor = UIColor.mnz_grayD8D8D8() + return control + }() + private let primaryButton: UIButton = { + let button = UIButton.newAutoLayout() + button.layer.cornerRadius = 8 + button.setTitleColor(UIColor.white, for: .normal) + button.titleLabel?.font = UIFont.mnz_SFUISemiBold(withSize: 17) + return button + }() + private let secondaryButton: UIButton = { + let button = UIButton.newAutoLayout() + button.titleLabel?.font = UIFont.mnz_SFUISemiBold(withSize: 17) + return button + }() + + private let contentView = UIView.newAutoLayout() + + private var didSetupConstraints = false + + override var shouldAutorotate: Bool { + return false + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + + + // MARK: Lifecycle + + override func loadView() { + view = UIView() + view.backgroundColor = UIColor.white + + view.addSubview(scrollView) + view.addSubview(pageControl) + view.addSubview(primaryButton) + view.addSubview(secondaryButton) + + scrollView.addSubview(contentView) + + view.setNeedsUpdateConstraints() + } + + override func viewDidLoad() { + switch type { + case .onboarding: + pageControl.currentPageIndicatorTintColor = UIColor.mnz_redMain() + pageControl.numberOfPages = 4 + pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged) + + primaryButton.setTitle("createAccount".localized(withComment: "Button title which triggers the action to create a MEGA account"), for: .normal) + primaryButton.backgroundColor = UIColor.mnz_redMain() + + secondaryButton.setTitle("login".localized(withComment: "Button title which triggers the action to login in your MEGA account"), for: .normal) + secondaryButton.setTitleColor(UIColor.mnz_redMain(), for: .normal) + + contentView.addSubview({ + let view = OnboardingInfoView(type: .encryptionInfo) + view.configureForAutoLayout() + return view + }()) + contentView.addSubview({ + let view = OnboardingInfoView(type: .chatInfo) + view.configureForAutoLayout() + return view + }()) + contentView.addSubview({ + let view = OnboardingInfoView(type: .contactsInfo) + view.configureForAutoLayout() + return view + }()) + contentView.addSubview({ + let view = OnboardingInfoView(type: .cameraUploadsInfo) + view.configureForAutoLayout() + return view + }()) + + case .permissions: + scrollView.isUserInteractionEnabled = false; + + pageControl.currentPageIndicatorTintColor = UIColor.mnz_green00BFA5() + pageControl.numberOfPages = 3 + pageControl.isUserInteractionEnabled = false; + + primaryButton.setTitle("Enable Access".localized(withComment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), for: .normal) + primaryButton.backgroundColor = UIColor.mnz_green00BFA5() + + secondaryButton.setTitle("notNow".localized(), for: .normal) + secondaryButton.setTitleColor(UIColor.mnz_green899B9C(), for: .normal) + + contentView.addSubview({ + let view = OnboardingInfoView(type: .photosPermission) + view.configureForAutoLayout() + return view + }()) + contentView.addSubview({ + let view = OnboardingInfoView(type: .microphoneAndCameraPermissions) + view.configureForAutoLayout() + return view + }()) + contentView.addSubview({ + let view = OnboardingInfoView(type: .notificationsPermission) + view.configureForAutoLayout() + return view + }()) + } + + scrollView.delegate = self + primaryButton.addTarget(self, action: #selector(primaryButtonTapped), for: .touchUpInside) + secondaryButton.addTarget(self, action: #selector(secondaryButtonTapped), for: .touchUpInside) + } + + override func updateViewConstraints() { + if !didSetupConstraints { + setupConstraints() + didSetupConstraints = true + } + super.updateViewConstraints() + } + + + + // MARK: Autolayout + + private func setupConstraints() { + let pageControlTopOffset:CGFloat = UIDevice.current.iPhone4X || UIDevice.current.iPhone5X ? -22 : -44 + let pageControlBottomOffset:CGFloat = UIDevice.current.iPhone4X || UIDevice.current.iPhone5X ? -29 : -58 + + scrollView.autoPinEdge(.top, to: .top, of: view, withOffset: 64) + scrollView.autoPinEdge(toSuperviewEdge: .left) + scrollView.autoPinEdge(toSuperviewEdge: .right) + scrollView.autoPinEdge(.bottom, to: .top, of: pageControl, withOffset: pageControlTopOffset) + + pageControl.autoPinEdge(toSuperviewEdge: .left) + pageControl.autoPinEdge(toSuperviewEdge: .right) + pageControl.autoPinEdge(.bottom, to: .top, of: primaryButton, withOffset: pageControlBottomOffset) + pageControl.autoSetDimension(.height, toSize: 44) + + primaryButton.autoPinEdge(.left, to: .left, of: view, withOffset: 44) + primaryButton.autoPinEdge(.right, to: .right, of: view, withOffset: -44) + primaryButton.autoPinEdge(.bottom, to: .top, of: secondaryButton, withOffset: -16) + primaryButton.autoSetDimension(.height, toSize: 50) + + secondaryButton.autoPinEdge(.left, to: .left, of: view, withOffset: 44) + secondaryButton.autoPinEdge(.right, to: .right, of: view, withOffset: -44) + secondaryButton.autoPinEdge(.bottom, to: .bottom, of: view, withOffset: -35) + secondaryButton.autoSetDimension(.height, toSize: 50) + + contentView.autoPinEdgesToSuperviewEdges() + contentView.autoMatch(.height, to: .height, of: scrollView) + + for (index, pageView) in contentView.subviews.enumerated() { + pageView.autoPinEdge(toSuperviewEdge: .top) + pageView.autoPinEdge(toSuperviewEdge: .bottom) + pageView.autoMatch(.width, to: .width, of: scrollView) + + if index == 0 { + pageView.autoPinEdge(toSuperviewEdge: .left) + } + + if index == (contentView.subviews.count - 1) { + pageView.autoPinEdge(toSuperviewEdge: .right) + } + + if contentView.subviews.count > 1 && index < (contentView.subviews.count - 1) { + let nextPageView = contentView.subviews[index + 1] + pageView.autoPinEdge(.right, to: .left, of: nextPageView) + } + } + } + + + + // MARK: Private + + private func scrollTo(page: Int) { + let newX = CGFloat(page) * scrollView.frame.width; + scrollView.contentOffset = CGPoint(x: newX, y: 0) + pageControl.currentPage = page + } + + + + // MARK: UIScrollViewDelegate + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + let newPage = scrollView.contentOffset.x / scrollView.frame.width + pageControl.currentPage = Int(newPage) + } + + + + // MARK: Targets + + @objc func pageControlValueChanged() { + scrollTo(page: pageControl.currentPage) + } + + @objc func primaryButtonTapped() { + switch type { + case .onboarding: + let createAccountNC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CreateAccountNavigationControllerID") + present(createAccountNC, animated: true, completion: nil) + + case .permissions: + switch pageControl.currentPage { + case 0: + DevicePermissionsHelper.photosPermission { (_) in + self.scrollTo(page: 1) + } + + case 1: + DevicePermissionsHelper.audioPermission { (_) in + DevicePermissionsHelper.videoPermission(completionHandler: { (_) in + self.scrollTo(page: 2) + }) + } + + case 2: + DevicePermissionsHelper.notificationsPermission { (_) in + self.dismiss(animated: true) { + self.completion?() + } + } + + default: + dismiss(animated: true) { + self.completion?() + } + + } + } + } + + @objc func secondaryButtonTapped() { + switch type { + case .onboarding: + let createAccountNC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginNavigationControllerID") + present(createAccountNC, animated: true, completion: nil) + + case .permissions: + switch pageControl.currentPage { + case 0: + scrollTo(page: 1) + + case 1: + scrollTo(page: 2) + + case 2: + dismiss(animated: true) { + self.completion?() + } + + default: + dismiss(animated: true) { + self.completion?() + } + + } + } + } + +} diff --git a/iMEGA/MEGA-Bridging-Header.h b/iMEGA/MEGA-Bridging-Header.h new file mode 100644 index 0000000000..76ddb37ff4 --- /dev/null +++ b/iMEGA/MEGA-Bridging-Header.h @@ -0,0 +1,9 @@ + +#import +#import "LocalizationSystem.h" + +#import "DevicePermissionsHelper.h" + +#import "UIColor+MNZCategory.h" +#import "UIDevice+MNZCategory.h" +#import "UIFont+MNZCategory.h" diff --git a/iMEGA/My Account/Upgrade/ProductDetailViewController.m b/iMEGA/My Account/Upgrade/ProductDetailViewController.m index 7b6a3ee7cf..c2f09e3c70 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailViewController.m +++ b/iMEGA/My Account/Upgrade/ProductDetailViewController.m @@ -1,7 +1,6 @@ #import "ProductDetailViewController.h" -#import "CameraUploadsPopUpViewController.h" #import "ProductDetailTableViewCell.h" #import "MEGANavigationController.h" diff --git a/iMEGA/Utils/DevicePermissionsHelper.h b/iMEGA/Utils/DevicePermissionsHelper.h index 2f79d410ba..ef7b23169a 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.h +++ b/iMEGA/Utils/DevicePermissionsHelper.h @@ -4,6 +4,8 @@ + (void)audioPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; + (void)videoPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; ++ (void)photosPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; ++ (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; + (UIAlertController*)audioPermisionAlertController; + (UIAlertController*)videoPermisionAlertController; diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index 273081a8c5..181e14cff3 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -1,6 +1,8 @@ #import "DevicePermissionsHelper.h" #import +#import +#import @implementation DevicePermissionsHelper @@ -24,16 +26,6 @@ + (void)audioPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { } } -+ (UIAlertController *)audioPermisionAlertController { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"microphonePermissions", @"Alert message to remember that MEGA app needs permission to use the Microphone to make calls and record videos and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - return permissionsAlertController; -} - + (void)videoPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL permissionGranted) { @@ -54,6 +46,43 @@ + (void)videoPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { } } ++ (void)photosPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + if (handler) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(status == PHAuthorizationStatusAuthorized); + }); + } + }]; +} + ++ (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL))handler { + if (@available(iOS 10.0, *)) { + [UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { + if (handler) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(granted); + }); + } + }]; + } else { + [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]]; + if (handler) { + handler(NO); + } + } +} + ++ (UIAlertController *)audioPermisionAlertController { + UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"microphonePermissions", @"Alert message to remember that MEGA app needs permission to use the Microphone to make calls and record videos and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; + [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + + [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + }]]; + return permissionsAlertController; +} + + (UIAlertController *)videoPermisionAlertController { UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"cameraPermissions", @"Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index 3b75d26ec8..146e243572 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -1208,14 +1208,7 @@ + (void)logout { [Helper deleteUserData]; [Helper deleteMasterKey]; - - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; - UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"initialViewControllerID"]; - UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; - [UIView transitionWithView:window duration:0.5 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent) animations:^{ - [window setRootViewController:viewController]; - } completion:nil]; - + [Helper resetCameraUploadsSettings]; [Helper resetUserData]; From 7028b61c3e033105735d8b0196487da0d12f1662 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Mon, 1 Oct 2018 17:34:04 +0200 Subject: [PATCH 02/58] Don't explain permissions to the user if they have already been set - New methods in DevicePermissionsHelper to determine the authorization status of each kind of permission. - Show only the permissions views corresponding to permissions that the user hasn't set yet. - Don't ask to Setup MEGA if all permissions has been set. --- iMEGA/API/Requests/MEGALoginRequestDelegate.m | 11 ++- iMEGA/AppDelegate.m | 4 +- iMEGA/Launch/InitialLaunchViewController.h | 8 -- iMEGA/Launch/InitialLaunchViewController.m | 4 - iMEGA/Launch/LaunchViewController.h | 8 ++ iMEGA/Launch/LaunchViewController.m | 18 +++- iMEGA/Login/OnboardingInfoView.swift | 2 + iMEGA/Login/OnboardingViewController.swift | 95 +++++++++---------- iMEGA/Utils/DevicePermissionsHelper.h | 6 ++ iMEGA/Utils/DevicePermissionsHelper.m | 46 +++++++++ 10 files changed, 135 insertions(+), 67 deletions(-) diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.m b/iMEGA/API/Requests/MEGALoginRequestDelegate.m index 7632ea2cee..8a49ba75d0 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.m @@ -4,6 +4,7 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" +#import "DevicePermissionsHelper.h" #import "InitialLaunchViewController.h" #import "Helper.h" #import "MEGAStore.h" @@ -127,9 +128,15 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SAMKeychain setPassword:session forService:@"MEGA" account:@"sessionV3"]; [[MEGAStore shareInstance] configureMEGAStore]; + LaunchViewController *launchVC; + if ([DevicePermissionsHelper shouldSetupPermissions]) { + launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"InitialLaunchViewControllerID"]; + } else { + launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"LaunchViewControllerID"]; + } + + launchVC.delegate = (id)UIApplication.sharedApplication.delegate; UIWindow *window = UIApplication.sharedApplication.delegate.window; - InitialLaunchViewController *launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"InitialLaunchViewControllerID"]; - launchVC.delegate = (id)UIApplication.sharedApplication.delegate; [UIView transitionWithView:window duration:0.5 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent) animations:^{ [window setRootViewController:launchVC]; } completion:nil]; diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index bb37a63667..9ca5818d47 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -77,7 +77,7 @@ #define kFirstRun @"FirstRun" -@interface AppDelegate () { +@interface AppDelegate () { BOOL isAccountFirstLogin; BOOL isFetchNodesDone; @@ -1852,7 +1852,7 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI } } -#pragma mark - InitialLaunchViewControllerDelegate +#pragma mark - LaunchViewControllerDelegate - (void)setupFinished { [self showMainTabBar]; diff --git a/iMEGA/Launch/InitialLaunchViewController.h b/iMEGA/Launch/InitialLaunchViewController.h index 93ba13a8e3..83c3f6756c 100644 --- a/iMEGA/Launch/InitialLaunchViewController.h +++ b/iMEGA/Launch/InitialLaunchViewController.h @@ -3,16 +3,8 @@ NS_ASSUME_NONNULL_BEGIN -@protocol InitialLaunchViewControllerDelegate - -- (void)setupFinished; - -@end - @interface InitialLaunchViewController : LaunchViewController -@property (nonatomic, weak) id delegate; - @end NS_ASSUME_NONNULL_END diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index bb311afdbb..44f59918e4 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -3,8 +3,6 @@ #import "MEGA-Swift.h" -#import "MEGASdkManager.h" - @interface InitialLaunchViewController () @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -23,8 +21,6 @@ - (void)viewDidLoad { self.descriptionLabel.text = AMLocalizedString(@"To fully take advantage of your MEGA account we need to ask you some permissions.", @"Detailed explanation of why the user should give some permissions to MEGA"); [self.setupButton setTitle:AMLocalizedString(@"Setup MEGA", @"Button which triggers the initial setup") forState:UIControlStateNormal]; [self.skipButton setTitle:AMLocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; - - [[MEGASdkManager sharedMEGASdk] addMEGARequestDelegate:self]; } #pragma mark - Private diff --git a/iMEGA/Launch/LaunchViewController.h b/iMEGA/Launch/LaunchViewController.h index 502712bfb3..9fbe9ebe0d 100644 --- a/iMEGA/Launch/LaunchViewController.h +++ b/iMEGA/Launch/LaunchViewController.h @@ -1,6 +1,12 @@ #import +@protocol LaunchViewControllerDelegate + +- (void)setupFinished; + +@end + @interface LaunchViewController : UIViewController @property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @@ -8,4 +14,6 @@ @property (weak, nonatomic) IBOutlet UILabel *label; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView; +@property (nonatomic, weak) id delegate; + @end diff --git a/iMEGA/Launch/LaunchViewController.m b/iMEGA/Launch/LaunchViewController.m index e35397d959..d206d0ba47 100644 --- a/iMEGA/Launch/LaunchViewController.m +++ b/iMEGA/Launch/LaunchViewController.m @@ -1,9 +1,11 @@ #import "LaunchViewController.h" +#import "MEGASdkManager.h" + #import "UIColor+MNZCategory.h" -@interface LaunchViewController () +@interface LaunchViewController () @end @@ -23,6 +25,8 @@ - (void)viewDidLoad { self.circularShapeLayer.strokeColor = UIColor.mnz_redMain.CGColor; self.circularShapeLayer.fillColor = UIColor.clearColor.CGColor; self.circularShapeLayer.lineWidth = 2.0f; + + [[MEGASdkManager sharedMEGASdk] addMEGARequestDelegate:self]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { @@ -37,4 +41,16 @@ - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } +#pragma mark - MEGARequestDelegate + +- (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { + if (error.type) { + return; + } + + if (request.type == MEGARequestTypeFetchNodes) { + [self.delegate setupFinished]; + } +} + @end diff --git a/iMEGA/Login/OnboardingInfoView.swift b/iMEGA/Login/OnboardingInfoView.swift index 6a4060100e..a98f54fdab 100644 --- a/iMEGA/Login/OnboardingInfoView.swift +++ b/iMEGA/Login/OnboardingInfoView.swift @@ -13,6 +13,7 @@ enum OnboardingInfoViewType { class OnboardingInfoView: UIView { + let type:OnboardingInfoViewType private let imageView: UIImageView = { let view = UIImageView.newAutoLayout() view.contentMode = .scaleAspectFit @@ -42,6 +43,7 @@ class OnboardingInfoView: UIView { // MARK: Lifecycle public init(type: OnboardingInfoViewType) { + self.type = type super.init(frame: CGRect.zero) switch type { diff --git a/iMEGA/Login/OnboardingViewController.swift b/iMEGA/Login/OnboardingViewController.swift index 3c7745e0a7..0aab2e7fc1 100644 --- a/iMEGA/Login/OnboardingViewController.swift +++ b/iMEGA/Login/OnboardingViewController.swift @@ -69,7 +69,6 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { switch type { case .onboarding: pageControl.currentPageIndicatorTintColor = UIColor.mnz_redMain() - pageControl.numberOfPages = 4 pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged) primaryButton.setTitle("createAccount".localized(withComment: "Button title which triggers the action to create a MEGA account"), for: .normal) @@ -103,7 +102,6 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { scrollView.isUserInteractionEnabled = false; pageControl.currentPageIndicatorTintColor = UIColor.mnz_green00BFA5() - pageControl.numberOfPages = 3 pageControl.isUserInteractionEnabled = false; primaryButton.setTitle("Enable Access".localized(withComment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), for: .normal) @@ -112,24 +110,31 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { secondaryButton.setTitle("notNow".localized(), for: .normal) secondaryButton.setTitleColor(UIColor.mnz_green899B9C(), for: .normal) - contentView.addSubview({ - let view = OnboardingInfoView(type: .photosPermission) - view.configureForAutoLayout() - return view - }()) - contentView.addSubview({ - let view = OnboardingInfoView(type: .microphoneAndCameraPermissions) - view.configureForAutoLayout() - return view - }()) - contentView.addSubview({ - let view = OnboardingInfoView(type: .notificationsPermission) - view.configureForAutoLayout() - return view - }()) + if DevicePermissionsHelper.shouldAskForPhotosPermissions() { + contentView.addSubview({ + let view = OnboardingInfoView(type: .photosPermission) + view.configureForAutoLayout() + return view + }()) + } + if DevicePermissionsHelper.shouldAskForAudioPermissions() || DevicePermissionsHelper.shouldAskForVideoPermissions() { + contentView.addSubview({ + let view = OnboardingInfoView(type: .microphoneAndCameraPermissions) + view.configureForAutoLayout() + return view + }()) + } + if DevicePermissionsHelper.shouldAskForNotificationsPermissions() { + contentView.addSubview({ + let view = OnboardingInfoView(type: .notificationsPermission) + view.configureForAutoLayout() + return view + }()) + } } scrollView.delegate = self + pageControl.numberOfPages = contentView.subviews.count primaryButton.addTarget(self, action: #selector(primaryButtonTapped), for: .touchUpInside) secondaryButton.addTarget(self, action: #selector(secondaryButtonTapped), for: .touchUpInside) } @@ -203,6 +208,17 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { pageControl.currentPage = page } + private func nextPageOrDismiss() { + let nextPage = self.pageControl.currentPage + 1 + if nextPage < self.pageControl.numberOfPages { + self.scrollTo(page: nextPage) + } else { + self.dismiss(animated: true) { + self.completion?() + } + } + } + // MARK: UIScrollViewDelegate @@ -227,31 +243,27 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { present(createAccountNC, animated: true, completion: nil) case .permissions: - switch pageControl.currentPage { - case 0: + let currentView: OnboardingInfoView = contentView.subviews[pageControl.currentPage] as! OnboardingInfoView + switch currentView.type { + case .photosPermission: DevicePermissionsHelper.photosPermission { (_) in - self.scrollTo(page: 1) + self.nextPageOrDismiss() } - case 1: + case .microphoneAndCameraPermissions: DevicePermissionsHelper.audioPermission { (_) in - DevicePermissionsHelper.videoPermission(completionHandler: { (_) in - self.scrollTo(page: 2) - }) + DevicePermissionsHelper.videoPermission { (_) in + self.nextPageOrDismiss() + } } - case 2: + case .notificationsPermission: DevicePermissionsHelper.notificationsPermission { (_) in - self.dismiss(animated: true) { - self.completion?() - } + self.nextPageOrDismiss() } default: - dismiss(animated: true) { - self.completion?() - } - + nextPageOrDismiss() } } } @@ -263,24 +275,7 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { present(createAccountNC, animated: true, completion: nil) case .permissions: - switch pageControl.currentPage { - case 0: - scrollTo(page: 1) - - case 1: - scrollTo(page: 2) - - case 2: - dismiss(animated: true) { - self.completion?() - } - - default: - dismiss(animated: true) { - self.completion?() - } - - } + nextPageOrDismiss() } } diff --git a/iMEGA/Utils/DevicePermissionsHelper.h b/iMEGA/Utils/DevicePermissionsHelper.h index ef7b23169a..6dd452afe4 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.h +++ b/iMEGA/Utils/DevicePermissionsHelper.h @@ -10,4 +10,10 @@ + (UIAlertController*)audioPermisionAlertController; + (UIAlertController*)videoPermisionAlertController; ++ (BOOL)shouldAskForAudioPermissions; ++ (BOOL)shouldAskForVideoPermissions; ++ (BOOL)shouldAskForPhotosPermissions; ++ (BOOL)shouldAskForNotificationsPermissions; ++ (BOOL)shouldSetupPermissions; + @end diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index 181e14cff3..1a29105a28 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -93,4 +93,50 @@ + (UIAlertController *)videoPermisionAlertController { return permissionsAlertController; } ++ (BOOL)shouldAskForAudioPermissions { + if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { + return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusNotDetermined; + } + return NO; +} + ++ (BOOL)shouldAskForVideoPermissions { + if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { + return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusNotDetermined; + } + return NO; +} + ++ (BOOL)shouldAskForPhotosPermissions { + return [PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusNotDetermined; +} + ++ (BOOL)shouldAskForNotificationsPermissions { + __block BOOL shouldAskForNotificationsPermissions = NO; + if (@available(iOS 10.0, *)) { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + shouldAskForNotificationsPermissions = YES; + } + dispatch_semaphore_signal(semaphore); + }]; + double delayInSeconds = 10.0; + dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_semaphore_wait(semaphore, waitTime); + } else { + shouldAskForNotificationsPermissions = !UIApplication.sharedApplication.isRegisteredForRemoteNotifications; + } + return shouldAskForNotificationsPermissions; +} + ++ (BOOL)shouldSetupPermissions { + BOOL shouldAskForAudioPermissions = [self shouldAskForAudioPermissions]; + BOOL shouldAskForVideoPermissions = [self shouldAskForVideoPermissions]; + BOOL shouldAskForPhotosPermissions = [self shouldAskForPhotosPermissions]; + BOOL shouldAskForNotificationsPermissions = [self shouldAskForNotificationsPermissions]; + + return shouldAskForAudioPermissions || shouldAskForVideoPermissions || shouldAskForPhotosPermissions || shouldAskForNotificationsPermissions; +} + @end From c805d69c5a620b65eebbbeceb1c33bf03be8374d Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 3 Oct 2018 18:15:54 +0200 Subject: [PATCH 03/58] Warn the user to enable permissions in Settings.app when needed - Do it always in DevicePermissionsHelper class. - Don't ask for notifications permission out of the new permissions setup screens. - Don't show the image picker if the user hasn't granted the permission to access photos. - Minor code refactoring and some code styling. --- MEGA.xcodeproj/project.pbxproj | 12 +++ iMEGA/AppDelegate.m | 43 +++------- .../CameraUploadsPopUpViewController.m | 51 +++-------- iMEGA/Chat/MessagesViewController.m | 62 ++++--------- iMEGA/Cloud drive/CloudDriveViewController.m | 69 +++++---------- iMEGA/Contacts/ContactLinkQRViewController.m | 2 +- iMEGA/Launch/LaunchViewController.m | 10 +++ iMEGA/Login/MainTabBarController.m | 4 +- iMEGA/Login/OnboardingInfoView.swift | 2 +- .../My Account/MyAccountBaseViewController.m | 68 +++++---------- .../Advanced/AdvancedTableViewController.m | 42 +++------ .../CameraUploadsTableViewController.m | 84 +++++++----------- .../Language/LanguageTableViewController.m | 66 +++++++------- iMEGA/Utils/DevicePermissionsHelper.h | 6 +- iMEGA/Utils/DevicePermissionsHelper.m | 86 +++++++++++-------- 15 files changed, 243 insertions(+), 364 deletions(-) diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index d6a6fd9da5..e08aad0589 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -280,6 +280,8 @@ 7720C5D3206AB2EB00F995D1 /* SF-UI-Text-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429DB2020A5E6000E0DCB /* SF-UI-Text-Semibold.otf */; }; 77264F242147D60600DC6BCB /* MEGACopyRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B2E91911F1F98F4003C6920 /* MEGACopyRequestDelegate.m */; }; 773DF39C20EF6902004C3418 /* MEGACopyRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B2E91911F1F98F4003C6920 /* MEGACopyRequestDelegate.m */; }; + 774089682164AAF000B93D3E /* CustomModalAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A819B33E1EAFA7E2004592F9 /* CustomModalAlertViewController.m */; }; + 774089692164AB1300B93D3E /* CustomModalAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A819B33E1EAFA7E2004592F9 /* CustomModalAlertViewController.m */; }; 7747B2D8208625B50011814E /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7747B2D6208623660011814E /* Reachability.m */; }; 7747B2D9208625B50011814E /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7747B2D6208623660011814E /* Reachability.m */; }; 7747B2DA208625B60011814E /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7747B2D6208623660011814E /* Reachability.m */; }; @@ -311,6 +313,9 @@ 77B3227C20A343A40037FA89 /* CopyrightWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 946B2D181F46E5B9009ED32C /* CopyrightWarningViewController.m */; }; 77B3227F20A436D60037FA89 /* NSURL+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */; }; 77B3AE0F215D0588008E889A /* InitialLaunchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */; }; + 77B3AE202163BD1B008E889A /* DevicePermissionsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 830EF9A320062F3800FAA947 /* DevicePermissionsHelper.m */; }; + 77B3AE212163BD2B008E889A /* DevicePermissionsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 830EF9A320062F3800FAA947 /* DevicePermissionsHelper.m */; }; + 77B3AE232163BDB8008E889A /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77B3AE222163BDB8008E889A /* UserNotifications.framework */; }; 77C544FF2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 77C544FE2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m */; }; 77CAAAE0202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77CAAADF202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m */; }; 77CAAAE3202C72F8004B16ED /* MEGAPhotoBrowserPickerCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 77CAAAE2202C72F8004B16ED /* MEGAPhotoBrowserPickerCollectionViewCell.m */; }; @@ -1231,6 +1236,7 @@ 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MNZCategory.m"; sourceTree = ""; }; 77B3AE0D215D0588008E889A /* InitialLaunchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InitialLaunchViewController.h; sourceTree = ""; }; 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InitialLaunchViewController.m; sourceTree = ""; }; + 77B3AE222163BDB8008E889A /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 77C544FD2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAPhotoBrowserAnimator.h; sourceTree = ""; }; 77C544FE2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGAPhotoBrowserAnimator.m; sourceTree = ""; }; 77CAAADE202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAPhotoBrowserPickerViewController.h; sourceTree = ""; }; @@ -1641,6 +1647,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 77B3AE232163BDB8008E889A /* UserNotifications.framework in Frameworks */, 7714901520F7592100769BD4 /* Contacts.framework in Frameworks */, 5B5974E4202859400011E819 /* SafariServices.framework in Frameworks */, A82A38D6200D332400883533 /* libmediainfo.a in Frameworks */, @@ -2256,6 +2263,7 @@ 418851EE1A66826800FDBA15 /* Frameworks */ = { isa = PBXGroup; children = ( + 77B3AE222163BDB8008E889A /* UserNotifications.framework */, 7714901420F7592000769BD4 /* Contacts.framework */, 777E9228203EB0E100266229 /* CoreImage.framework */, 5B5974E2202858050011E819 /* SafariServices.framework */, @@ -4094,6 +4102,7 @@ 945605471EDD67520094EF0F /* MEGASdkManager.m in Sources */, 9432292C1EDD8542007DB542 /* CameraUploadsTableViewController.m in Sources */, 945605521EDD7E020094EF0F /* GetLinkActivity.m in Sources */, + 77B3AE212163BD2B008E889A /* DevicePermissionsHelper.m in Sources */, 945605441EDD66CE0094EF0F /* UIColor+MNZCategory.m in Sources */, 5BFA34711FAC5BFB005BFC4E /* MEGARemoveRequestDelegate.m in Sources */, 94624CFF1F50518900D52504 /* MEGABaseRequestDelegate.m in Sources */, @@ -4106,6 +4115,7 @@ 943229371EDD88B1007DB542 /* SaveToCameraRollActivity.m in Sources */, 832F19EF2091BD980067679E /* MOMediaDestination+CoreDataClass.m in Sources */, 834AD6092024697600D2527F /* MGSwipeTableCell.m in Sources */, + 774089692164AB1300B93D3E /* CustomModalAlertViewController.m in Sources */, 77B3227B20A343A30037FA89 /* CopyrightWarningViewController.m in Sources */, 945605401EDD65400094EF0F /* BrowserViewController.m in Sources */, 9456053E1EDD54F10094EF0F /* SAMKeychainQuery.m in Sources */, @@ -4159,6 +4169,7 @@ 77892E9D20E5012C0020A533 /* ChatRoomCell.m in Sources */, 94892C911EFBEA2300AEAC25 /* SAMKeychainQuery.m in Sources */, A824A91C2039F50B00D777D2 /* SDAVAssetExportSession.m in Sources */, + 774089682164AAF000B93D3E /* CustomModalAlertViewController.m in Sources */, 7747B2DB208625B70011814E /* Reachability.m in Sources */, 8361359221074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.m in Sources */, A81F2C6A1F334FB2008600A5 /* MEGAProcessAsset.m in Sources */, @@ -4193,6 +4204,7 @@ 94892C981EFBF03800AEAC25 /* SVProgressHUD.m in Sources */, 5B2E78CD1FA0C3310096C9BE /* MEGACreateFolderRequestDelegate.m in Sources */, 941135581F01005D00D33428 /* CameraUploads.m in Sources */, + 77B3AE202163BD1B008E889A /* DevicePermissionsHelper.m in Sources */, 941135501F00FFAB00D33428 /* BrowserViewController.m in Sources */, 94892CA11EFBF20800AEAC25 /* UIFont+MNZCategory.m in Sources */, 8361359521074C5D002FA3CC /* MOUploadTransfer+CoreDataProperties.m in Sources */, diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index 9ca5818d47..c7e0d0ab91 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -359,8 +359,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( } if ([CameraUploads syncManager].isCameraUploadsEnabled) { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - if (status == PHAuthorizationStatusDenied) { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (!granted) { MEGALogInfo(@"Disable Camera Uploads"); [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; } @@ -582,14 +582,14 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct if (granted) { [self performCall]; } else { - [UIApplication.mnz_visibleViewController presentViewController:[DevicePermissionsHelper videoPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; } else { [self performCall]; } } else { - [UIApplication.mnz_visibleViewController presentViewController:[DevicePermissionsHelper audioPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; } @@ -1334,35 +1334,14 @@ - (void)registerForVoIPNotifications { } - (void)registerForNotifications { - if (@available(iOS 10.0, *)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - center.delegate = self; - [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) - completionHandler:^(BOOL granted, NSError * _Nullable error) { - if (!error) { - MEGALogInfo(@"Request notifications authorization succeeded"); - } - if (granted) { - [self notificationsSettings]; - } - }]; - } else { - [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings - settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | - UIUserNotificationTypeSound categories:nil]]; - } -} - -- (void)notificationsSettings { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { - MEGALogInfo(@"Notifications settings %@", settings); - if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) { - dispatch_async(dispatch_get_main_queue(), ^(void) { + UNUserNotificationCenter.currentNotificationCenter.delegate = self; + if (![DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + [DevicePermissionsHelper notificationsPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { [[UIApplication sharedApplication] registerForRemoteNotifications]; - }); - } - }]; + } + }]; + } } - (void)presentNode { diff --git a/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m b/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m index 02e63c59fe..1217e7b102 100644 --- a/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m +++ b/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m @@ -4,6 +4,7 @@ #import "SVProgressHUD.h" +#import "DevicePermissionsHelper.h" #import "MEGAReachabilityManager.h" #import "UIDevice+MNZCategory.h" @@ -76,43 +77,19 @@ - (IBAction)skipTouchUpInside:(UIButton *)sender { } - (IBAction)enableTouchUpInside:(UIButton *)sender { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusNotDetermined: - break; - - case PHAuthorizationStatusAuthorized: { - MEGALogInfo(@"Enable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[CameraUploads syncManager].isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; - - [self dismissViewControllerAnimated:YES completion:^{ - [SVProgressHUD showImage:[UIImage imageNamed:@"hudCameraUploads"] status:AMLocalizedString(@"cameraUploadsEnabled", nil)]; - }]; - break; - } - - case PHAuthorizationStatusRestricted: - break; - - case PHAuthorizationStatusDenied:{ - [self dismissViewControllerAnimated:YES completion:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:permissionsAlertController animated:YES completion:nil]; - - }); - break; - } - default: - break; + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + MEGALogInfo(@"Enable Camera Uploads"); + [[CameraUploads syncManager] setIsCameraUploadsEnabled:YES]; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[CameraUploads syncManager].isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; + + [self dismissViewControllerAnimated:YES completion:^{ + [SVProgressHUD showImage:[UIImage imageNamed:@"hudCameraUploads"] status:AMLocalizedString(@"cameraUploadsEnabled", nil)]; + }]; + } else { + [self dismissViewControllerAnimated:YES completion:^{ + [DevicePermissionsHelper warnAboutPhotosPermission]; + }]; } }]; } diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index d6f5588fd9..a2b19adf10 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -465,14 +465,14 @@ - (void)startAudioVideoCall:(UIBarButtonItem *)sender { if (granted) { [self openCallViewWithVideo:sender.tag]; } else { - [self presentViewController:[DevicePermissionsHelper videoPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; } else { [self openCallViewWithVideo:sender.tag]; } } else { - [self presentViewController:[DevicePermissionsHelper audioPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; } @@ -1326,49 +1326,21 @@ - (void)didPressAccessoryButton:(UIButton *)sender { switch (sender.tag) { case MEGAChatAccessoryButtonCamera: { self.inputToolbar.hidden = YES; - if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL permissionGranted) { - if (permissionGranted) { - dispatch_async(dispatch_get_main_queue(), ^{ - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusAuthorized: { - dispatch_async(dispatch_get_main_queue(), ^{ - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - case PHAuthorizationStatusNotDetermined: - case PHAuthorizationStatusRestricted: - case PHAuthorizationStatusDenied:{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - default: - break; - } - }]; - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"cameraPermissions", @"Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:permissionsAlertController animated:YES completion:nil]; - }); - } - }]; - } + [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } else { + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } + }]; + } else { + [DevicePermissionsHelper warnAboutPhotosPermission]; + } + }]; break; } diff --git a/iMEGA/Cloud drive/CloudDriveViewController.m b/iMEGA/Cloud drive/CloudDriveViewController.m index 689d89221c..c70dcb119c 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController.m +++ b/iMEGA/Cloud drive/CloudDriveViewController.m @@ -1,3 +1,4 @@ + #import "CloudDriveViewController.h" #import @@ -13,6 +14,7 @@ #import "UIApplication+MNZCategory.h" #import "UIImageView+MNZCategory.h" +#import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGACreateFolderRequestDelegate.h" #import "MEGAMoveRequestDelegate.h" @@ -950,11 +952,13 @@ - (void)showImagePickerForSourceType:(UIImagePickerControllerSourceType)sourceTy MEGAImagePickerController *imagePickerController = [[MEGAImagePickerController alloc] initToUploadWithParentNode:self.parentNode sourceType:sourceType]; [self presentViewController:imagePickerController animated:YES completion:nil]; } else { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - dispatch_async(dispatch_get_main_queue(), ^{ + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { MEGAAssetsPickerController *pickerViewController = [[MEGAAssetsPickerController alloc] initToUploadToCloudDriveWithParentNode:self.parentNode]; [self presentViewController:pickerViewController animated:YES completion:nil]; - }); + } else { + [DevicePermissionsHelper warnAboutPhotosPermission]; + } }]; } } @@ -1123,50 +1127,21 @@ - (void)presentUploadAlertController { [uploadAlertController addAction:fromPhotosAlertAction]; UIAlertAction *captureAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"capturePhotoVideo", @"Menu option from the `Add` section that allows the user to capture a video or a photo and upload it directly to MEGA.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL permissionGranted) { - if (permissionGranted) { - dispatch_async(dispatch_get_main_queue(), ^{ - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusAuthorized: { - dispatch_async(dispatch_get_main_queue(), ^{ - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - case PHAuthorizationStatusNotDetermined: - case PHAuthorizationStatusRestricted: - case PHAuthorizationStatusDenied:{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - default: - break; - } - }]; - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"cameraPermissions", @"Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:permissionsAlertController animated:YES completion:nil]; - }); - } - }]; - } + [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } else { + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } + }]; + } else { + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; + } + }]; }]; [captureAlertAction mnz_setTitleTextColor:[UIColor mnz_black333333]]; [uploadAlertController addAction:captureAlertAction]; diff --git a/iMEGA/Contacts/ContactLinkQRViewController.m b/iMEGA/Contacts/ContactLinkQRViewController.m index 8157e5e339..d825139bab 100644 --- a/iMEGA/Contacts/ContactLinkQRViewController.m +++ b/iMEGA/Contacts/ContactLinkQRViewController.m @@ -172,7 +172,7 @@ - (IBAction)valueChangedAtSegmentedControl:(UISegmentedControl *)sender { } else { sender.selectedSegmentIndex = 0; [self valueChangedAtSegmentedControl:sender]; - [self presentViewController:[DevicePermissionsHelper videoPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } break; diff --git a/iMEGA/Launch/LaunchViewController.m b/iMEGA/Launch/LaunchViewController.m index d206d0ba47..a764c5b021 100644 --- a/iMEGA/Launch/LaunchViewController.m +++ b/iMEGA/Launch/LaunchViewController.m @@ -25,10 +25,20 @@ - (void)viewDidLoad { self.circularShapeLayer.strokeColor = UIColor.mnz_redMain.CGColor; self.circularShapeLayer.fillColor = UIColor.clearColor.CGColor; self.circularShapeLayer.lineWidth = 2.0f; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; [[MEGASdkManager sharedMEGASdk] addMEGARequestDelegate:self]; } +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [[MEGASdkManager sharedMEGASdk] removeMEGARequestDelegate:self]; +} + - (UIInterfaceOrientationMask)supportedInterfaceOrientations { if ([[UIDevice currentDevice] iPhone4X] || [[UIDevice currentDevice] iPhone5X]) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index dd36deddf7..d988f98481 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -277,14 +277,14 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { if (granted) { [self presentRingingCall:api call:[api chatCallForCallId:call.callId]]; } else { - [self presentViewController:[DevicePermissionsHelper videoPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; } else { [self presentRingingCall:api call:[api chatCallForCallId:call.callId]]; } } else { - [self presentViewController:[DevicePermissionsHelper audioPermisionAlertController] animated:YES completion:nil]; + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; } }]; break; diff --git a/iMEGA/Login/OnboardingInfoView.swift b/iMEGA/Login/OnboardingInfoView.swift index a98f54fdab..eec28041f6 100644 --- a/iMEGA/Login/OnboardingInfoView.swift +++ b/iMEGA/Login/OnboardingInfoView.swift @@ -68,7 +68,7 @@ class OnboardingInfoView: UIView { titleLabel.text = "Allow Access to Photos".localized(withComment: "Title label that explains that the user is going to be asked for the photos permission") descriptionLabel.text = "To share photos and videos, allow MEGA to access your photos".localized(withComment: "Detailed explanation of why the user should give permission to access to the photos") case .microphoneAndCameraPermissions: - imageView.image = UIImage(named: "privacy_warning_ico") + imageView.image = UIImage(named: "groupChat") titleLabel.text = "Enable Microphone and Camera".localized(withComment: "Title label that explains that the user is going to be asked for the microphone and camera permission") descriptionLabel.text = "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone".localized(withComment: "Detailed explanation of why the user should give permission to access to the camera and the microphone") case .notificationsPermission: diff --git a/iMEGA/My Account/MyAccountBaseViewController.m b/iMEGA/My Account/MyAccountBaseViewController.m index 122ff6232e..21a591062e 100644 --- a/iMEGA/My Account/MyAccountBaseViewController.m +++ b/iMEGA/My Account/MyAccountBaseViewController.m @@ -8,6 +8,7 @@ #import "SVProgressHUD.h" #import "ContactLinkQRViewController.h" +#import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGAImagePickerController.h" #import "MEGANavigationController.h" @@ -110,55 +111,32 @@ - (void)presentChangeAvatarAlertController { [changeAvatarAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; UIAlertAction *fromPhotosAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"choosePhotoVideo", @"Menu option from the `Add` section that allows the user to choose a photo or video to upload it to MEGA") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; + } else { + [DevicePermissionsHelper warnAboutPhotosPermission]; + } + }]; }]; [fromPhotosAlertAction mnz_setTitleTextColor:[UIColor mnz_black333333]]; [changeAvatarAlertController addAction:fromPhotosAlertAction]; UIAlertAction *captureAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"capturePhotoVideo", @"Menu option from the `Add` section that allows the user to capture a video or a photo and upload it directly to MEGA.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType: completionHandler:)]) { - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL permissionGranted) { - if (permissionGranted) { - // Permission has been granted. Use dispatch_async for any UI updating code because this block may be executed in a thread. - dispatch_async(dispatch_get_main_queue(), ^{ - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusAuthorized: { - dispatch_async(dispatch_get_main_queue(), ^{ - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - case PHAuthorizationStatusNotDetermined: - case PHAuthorizationStatusRestricted: - case PHAuthorizationStatusDenied:{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; - }); - break; - } - - default: - break; - } - }]; - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *cameraPermissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"cameraPermissions", @"Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - [cameraPermissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [cameraPermissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - //Check Camera permissions - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:cameraPermissionsAlertController animated:YES completion:nil]; - }); - } - }]; - } + [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } else { + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; + } + }]; + } else { + [DevicePermissionsHelper warnAboutAudioAndVideoPermissions]; + } + }]; }]; [captureAlertAction mnz_setTitleTextColor:[UIColor mnz_black333333]]; [changeAvatarAlertController addAction:captureAlertAction]; diff --git a/iMEGA/Settings/Advanced/AdvancedTableViewController.m b/iMEGA/Settings/Advanced/AdvancedTableViewController.m index 539346618a..c9a68871d5 100644 --- a/iMEGA/Settings/Advanced/AdvancedTableViewController.m +++ b/iMEGA/Settings/Advanced/AdvancedTableViewController.m @@ -5,6 +5,7 @@ #import "SVProgressHUD.h" +#import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGAMultiFactorAuthCheckRequestDelegate.h" #import "MEGAReachabilityManager.h" @@ -200,41 +201,18 @@ - (IBAction)videosSwitchValueChanged:(UISwitch *)sender { } - (IBAction)mediaInGallerySwitchChanged:(UISwitch *)sender { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusNotDetermined: - break; - case PHAuthorizationStatusAuthorized: { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [[NSUserDefaults standardUserDefaults] setBool:sender.on forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } else { + if (self.saveMediaInGallerySwitch.isOn) { + [DevicePermissionsHelper warnAboutPhotosPermission]; + [self.saveMediaInGallerySwitch setOn:NO animated:YES]; + } else { [[NSUserDefaults standardUserDefaults] setBool:sender.on forKey:@"isSaveMediaCapturedToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] synchronize]; - break; } - case PHAuthorizationStatusRestricted: { - break; - } - case PHAuthorizationStatusDenied:{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.saveMediaInGallerySwitch.isOn) { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:permissionsAlertController animated:YES completion:nil]; - - [self.saveMediaInGallerySwitch setOn:NO animated:YES]; - } else { - [[NSUserDefaults standardUserDefaults] setBool:sender.on forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - }); - break; - } - default: - break; } }]; } diff --git a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m index 7c05642db2..81a2455a89 100644 --- a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m +++ b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m @@ -1,14 +1,15 @@ + #import "CameraUploadsTableViewController.h" #import +#import "CameraUploads.h" +#import "DevicePermissionsHelper.h" +#import "Helper.h" #import "MEGAReachabilityManager.h" #import "MEGATransfer+MNZCategory.h" #import "NSString+MNZCategory.h" -#import "CameraUploads.h" -#import "Helper.h" - @interface CameraUploadsTableViewController () @property (weak, nonatomic) IBOutlet UILabel *enableCameraUploadsLabel; @@ -85,59 +86,34 @@ - (IBAction)enableCameraUploadsSwitchValueChanged:(UISwitch *)sender { } } - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusNotDetermined: - break; - case PHAuthorizationStatusAuthorized: { - dispatch_async(dispatch_get_main_queue(), ^{ - BOOL isCameraUploadsEnabled = ![CameraUploads syncManager].isCameraUploadsEnabled; - if (isCameraUploadsEnabled) { - MEGALogInfo(@"Enable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; - } else { - MEGALogInfo(@"Disable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; - - [self.uploadVideosSwitch setOn:isCameraUploadsEnabled animated:YES]; - [self.useCellularConnectionSwitch setOn:isCameraUploadsEnabled animated:YES]; - [self.onlyWhenChargingSwitch setOn:isCameraUploadsEnabled animated:YES]; - } - - [self.tableView reloadData]; - }); - break; - } - case PHAuthorizationStatusRestricted: { - break; - } - case PHAuthorizationStatusDenied:{ - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - - [self presentViewController:permissionsAlertController animated:YES completion:nil]; - - MEGALogInfo(@"Disable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; - - [self.uploadVideosSwitch setOn:NO animated:YES]; - [self.useCellularConnectionSwitch setOn:NO animated:YES]; - [self.onlyWhenChargingSwitch setOn:NO animated:YES]; - [self.tableView reloadData]; - }); - break; + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + BOOL isCameraUploadsEnabled = ![CameraUploads syncManager].isCameraUploadsEnabled; + if (isCameraUploadsEnabled) { + MEGALogInfo(@"Enable Camera Uploads"); + [[CameraUploads syncManager] setIsCameraUploadsEnabled:YES]; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; + } else { + MEGALogInfo(@"Disable Camera Uploads"); + [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; + + [self.uploadVideosSwitch setOn:isCameraUploadsEnabled animated:YES]; + [self.useCellularConnectionSwitch setOn:isCameraUploadsEnabled animated:YES]; + [self.onlyWhenChargingSwitch setOn:isCameraUploadsEnabled animated:YES]; } - default: - break; + } else { + [DevicePermissionsHelper warnAboutPhotosPermission]; + + MEGALogInfo(@"Disable Camera Uploads"); + [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; + + [self.uploadVideosSwitch setOn:NO animated:YES]; + [self.useCellularConnectionSwitch setOn:NO animated:YES]; + [self.onlyWhenChargingSwitch setOn:NO animated:YES]; } + + [self.tableView reloadData]; }]; } diff --git a/iMEGA/Settings/Language/LanguageTableViewController.m b/iMEGA/Settings/Language/LanguageTableViewController.m index b8d96a4727..b2053f36f7 100644 --- a/iMEGA/Settings/Language/LanguageTableViewController.m +++ b/iMEGA/Settings/Language/LanguageTableViewController.m @@ -3,6 +3,7 @@ #import +#import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGASDKManager.h" #import "SelectableTableViewCell.h" @@ -89,39 +90,40 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath // Schedule a notification to make it easy to reopen MEGA: NSString *notificationText = AMLocalizedString(@"languageRestartNotification", @"Text shown in a notification to make it easy for the user to restart the app after the language is changed"); - if (@available(iOS 10.0, *)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound; - [center requestAuthorizationWithOptions:options - completionHandler:^(BOOL granted, NSError * _Nullable error) { - if (granted) { - UNMutableNotificationContent *content = [UNMutableNotificationContent new]; - content.body = notificationText; - content.sound = [UNNotificationSound defaultSound]; - UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 - repeats:NO]; - NSString *identifier = @"nz.mega"; - UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier - content:content trigger:trigger]; - [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { - exit(0); - }]; - } else { - exit(0); - } - }]; + if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + exit(0); } else { - UILocalNotification* localNotification = [[UILocalNotification alloc] init]; - localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1]; - localNotification.alertBody = notificationText; - localNotification.timeZone = [NSTimeZone defaultTimeZone]; - [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; - // The exit must be called some time after the previous method is called, because there is no way to - // know when the notification is properly scheduled, and calling exit inmediatley causes to not have - // it shown: - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - exit(0); - }); + [DevicePermissionsHelper notificationsPermissionWithCompletionHandler:^(BOOL granted) { + if (@available(iOS 10.0, *)) { + if (granted) { + UNMutableNotificationContent *content = [UNMutableNotificationContent new]; + content.body = notificationText; + content.sound = [UNNotificationSound defaultSound]; + UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 + repeats:NO]; + NSString *identifier = @"nz.mega"; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier + content:content trigger:trigger]; + [UNUserNotificationCenter.currentNotificationCenter addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + exit(0); + }]; + } else { + exit(0); + } + } else { + UILocalNotification* localNotification = [[UILocalNotification alloc] init]; + localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1]; + localNotification.alertBody = notificationText; + localNotification.timeZone = [NSTimeZone defaultTimeZone]; + [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; + // The exit must be called some time after the previous method is called, because there is no way to + // know when the notification is properly scheduled, and calling exit inmediatley causes to not have + // it shown: + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + exit(0); + }); + } + }]; } }]]; diff --git a/iMEGA/Utils/DevicePermissionsHelper.h b/iMEGA/Utils/DevicePermissionsHelper.h index 6dd452afe4..f9ec0c006a 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.h +++ b/iMEGA/Utils/DevicePermissionsHelper.h @@ -1,3 +1,4 @@ + #import @interface DevicePermissionsHelper : NSObject @@ -7,8 +8,9 @@ + (void)photosPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; + (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; -+ (UIAlertController*)audioPermisionAlertController; -+ (UIAlertController*)videoPermisionAlertController; ++ (void)warnAboutPhotosPermission; ++ (void)warnAboutAudioAndVideoPermissions; ++ (void)warnAboutNotificationsPermission; + (BOOL)shouldAskForAudioPermissions; + (BOOL)shouldAskForVideoPermissions; diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index 1a29105a28..7285b17454 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -1,25 +1,22 @@ + #import "DevicePermissionsHelper.h" #import #import #import +#import "CustomModalAlertViewController.h" +#import "UIApplication+MNZCategory.h" +#import "UIColor+MNZCategory.h" + @implementation DevicePermissionsHelper + (void)audioPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL permissionGranted) { - if (permissionGranted) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (handler) { - handler(YES); - } - }); - } else { + if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ - if (handler) { - handler(NO); - } + handler(permissionGranted); }); } }]; @@ -29,17 +26,9 @@ + (void)audioPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { + (void)videoPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL permissionGranted) { - if (permissionGranted) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (handler) { - handler(YES); - } - }); - } else { + if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ - if (handler) { - handler(NO); - } + handler(permissionGranted); }); } }]; @@ -73,24 +62,53 @@ + (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL))handler { } } -+ (UIAlertController *)audioPermisionAlertController { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"microphonePermissions", @"Alert message to remember that MEGA app needs permission to use the Microphone to make calls and record videos and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; ++ (void)warnAboutPhotosPermission { + CustomModalAlertViewController *permissionsModal = [self permissionsModal]; - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - return permissionsAlertController; + permissionsModal.image = [UIImage imageNamed:@"photosPermission"]; + permissionsModal.viewTitle = AMLocalizedString(@"Allow Access to Photos", @"Title label that explains that the user is going to be asked for the photos permission"); + permissionsModal.detail = AMLocalizedString(@"To share photos and videos, allow MEGA to access your photos", @"Detailed explanation of why the user should give permission to access to the photos"); + permissionsModal.action = AMLocalizedString(@"Enable Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand"); + permissionsModal.dismiss = AMLocalizedString(@"notNow", nil); + + [UIApplication.mnz_visibleViewController presentViewController:permissionsModal animated:YES completion:nil]; +} + ++ (void)warnAboutAudioAndVideoPermissions { + CustomModalAlertViewController *permissionsModal = [self permissionsModal]; + + permissionsModal.image = [UIImage imageNamed:@"groupChat"]; + permissionsModal.viewTitle = AMLocalizedString(@"Enable Microphone and Camera", @"Title label that explains that the user is going to be asked for the microphone and camera permission"); + permissionsModal.detail = AMLocalizedString(@"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", @"Detailed explanation of why the user should give permission to access to the camera and the microphone"); + permissionsModal.action = AMLocalizedString(@"Enable Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand"); + permissionsModal.dismiss = AMLocalizedString(@"notNow", nil); + + [UIApplication.mnz_visibleViewController presentViewController:permissionsModal animated:YES completion:nil]; } -+ (UIAlertController *)videoPermisionAlertController { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"cameraPermissions", @"Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it") preferredStyle:UIAlertControllerStyleAlert]; - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; ++ (void)warnAboutNotificationsPermission { + CustomModalAlertViewController *permissionsModal = [self permissionsModal]; + + permissionsModal.image = [UIImage imageNamed:@"privacy_warning_ico"]; + permissionsModal.viewTitle = AMLocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); + permissionsModal.detail = AMLocalizedString(@"We would like to send you notifications so you receive new messages on your device instantly.", @"Detailed explanation of why the user should give permission to deliver notifications"); + permissionsModal.action = AMLocalizedString(@"Enable Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand"); + permissionsModal.dismiss = AMLocalizedString(@"notNow", nil); - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; - }]]; - return permissionsAlertController; + [UIApplication.mnz_visibleViewController presentViewController:permissionsModal animated:YES completion:nil]; +} + ++ (CustomModalAlertViewController *)permissionsModal { + CustomModalAlertViewController *permissionsModal = [[CustomModalAlertViewController alloc] init]; + __weak CustomModalAlertViewController *weakPermissionsModal = permissionsModal; + permissionsModal.modalPresentationStyle = UIModalPresentationOverCurrentContext; + permissionsModal.actionColor = UIColor.mnz_green00BFA5; + permissionsModal.dismissColor = UIColor.mnz_green899B9C; + permissionsModal.completion = ^{ + [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + [weakPermissionsModal dismissViewControllerAnimated:YES completion:nil]; + }; + return permissionsModal; } + (BOOL)shouldAskForAudioPermissions { From e58d8dca32744811ea28fb9410d970d2b3dc7dae Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 3 Oct 2018 18:25:58 +0200 Subject: [PATCH 04/58] Minor UI change in Launch screen --- iMEGA/Launch/Launch.storyboard | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iMEGA/Launch/Launch.storyboard b/iMEGA/Launch/Launch.storyboard index 8891c67ba6..ab119fd7a3 100644 --- a/iMEGA/Launch/Launch.storyboard +++ b/iMEGA/Launch/Launch.storyboard @@ -50,7 +50,7 @@ @@ -157,7 +157,7 @@ - + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + + + - - - - - @@ -460,7 +432,7 @@ - + diff --git a/iMEGA/Camera uploads/PhotosViewController.m b/iMEGA/Camera uploads/PhotosViewController.m index 04d7599c8b..c3aa3a7136 100644 --- a/iMEGA/Camera uploads/PhotosViewController.m +++ b/iMEGA/Camera uploads/PhotosViewController.m @@ -101,6 +101,23 @@ - (void)viewWillAppear:(BOOL)animated { [self reloadUI]; } +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + self.cellSize = [self.photosCollectionView mnz_calculateCellSizeForInset:self.cellInset]; + [self reloadUI]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + if (![NSUserDefaults.standardUserDefaults objectForKey:kIsCameraUploadsEnabled]) { + MEGANavigationController *cameraUploadsNavigationController = [[UIStoryboard storyboardWithName:@"Photos" bundle:nil] instantiateViewControllerWithIdentifier:@"CameraUploadsPopUpNavigationControllerID"]; + + [self presentViewController:cameraUploadsNavigationController animated:YES completion:nil]; + } +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -111,13 +128,6 @@ - (void)viewWillDisappear:(BOOL)animated { [[MEGASdkManager sharedMEGASdk] removeMEGAGlobalDelegate:self]; } -- (void)viewDidLayoutSubviews { - [super viewDidLayoutSubviews]; - - self.cellSize = [self.photosCollectionView mnz_calculateCellSizeForInset:self.cellInset]; - [self reloadUI]; -} - - (UIInterfaceOrientationMask)supportedInterfaceOrientations { if ([[UIDevice currentDevice] iPhone4X] || [[UIDevice currentDevice] iPhone5X]) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; diff --git a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m index ade2c608e9..c2ed182820 100644 --- a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m +++ b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m @@ -89,15 +89,13 @@ - (IBAction)enableCameraUploadsSwitchValueChanged:(UISwitch *)sender { [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { BOOL isCameraUploadsEnabled = ![CameraUploads syncManager].isCameraUploadsEnabled; + [[CameraUploads syncManager] setIsCameraUploadsEnabled:isCameraUploadsEnabled]; + [NSUserDefaults.standardUserDefaults setObject:@(isCameraUploadsEnabled) forKey:kIsCameraUploadsEnabled]; + if (isCameraUploadsEnabled) { MEGALogInfo(@"Enable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; } else { MEGALogInfo(@"Disable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:isCameraUploadsEnabled] forKey:kIsCameraUploadsEnabled]; - [self.uploadVideosSwitch setOn:isCameraUploadsEnabled animated:YES]; [self.useCellularConnectionSwitch setOn:isCameraUploadsEnabled animated:YES]; [self.onlyWhenChargingSwitch setOn:isCameraUploadsEnabled animated:YES]; @@ -135,7 +133,7 @@ - (IBAction)uploadVideosSwitchValueChanged:(UISwitch *)sender { [self.uploadVideosSwitch setOn:[[CameraUploads syncManager] isUploadVideosEnabled] animated:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[CameraUploads syncManager].isUploadVideosEnabled] forKey:kIsUploadVideosEnabled]; + [NSUserDefaults.standardUserDefaults setObject:@([CameraUploads syncManager].isUploadVideosEnabled) forKey:kIsUploadVideosEnabled]; } - (IBAction)useCellularConnectionSwitchValueChanged:(UISwitch *)sender { @@ -151,7 +149,7 @@ - (IBAction)useCellularConnectionSwitchValueChanged:(UISwitch *)sender { } [self.useCellularConnectionSwitch setOn:[[CameraUploads syncManager] isUseCellularConnectionEnabled] animated:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[CameraUploads syncManager].isUseCellularConnectionEnabled] forKey:kIsUseCellularConnectionEnabled]; + [NSUserDefaults.standardUserDefaults setObject:@([CameraUploads syncManager].isUseCellularConnectionEnabled) forKey:kIsUseCellularConnectionEnabled]; } - (IBAction)onlyWhenChargindSwitchValueChanged:(UISwitch *)sender { @@ -166,7 +164,7 @@ - (IBAction)onlyWhenChargindSwitchValueChanged:(UISwitch *)sender { } [self.onlyWhenChargingSwitch setOn:[[CameraUploads syncManager] isOnlyWhenChargingEnabled] animated:YES]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[CameraUploads syncManager].isOnlyWhenChargingEnabled] forKey:kIsOnlyWhenChargingEnabled]; + [NSUserDefaults.standardUserDefaults setObject:@([CameraUploads syncManager].isOnlyWhenChargingEnabled) forKey:kIsOnlyWhenChargingEnabled]; } #pragma mark - UITableViewDataSource diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index 8e13c18c22..9fcf913cf4 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -1376,6 +1376,8 @@ + (void)resetUserData { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"TransfersPaused"]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kIsCameraUploadsEnabled]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kIsUploadVideosEnabled]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"IsSavePhotoToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"IsSaveVideoToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ChatVideoQuality"]; From f45e2b1de13023c2c0ae0addfcf722b55d86c809 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 10 Oct 2018 16:56:40 +0200 Subject: [PATCH 07/58] Added empty state to the assets picker toolbar - It is shown when users have not authorized access to the library. - Added class method to UIImage category to instantiate images with a custom size. - Some code styling. - Updated submodules. --- iMEGA/Categories/UIImage+MNZCategory.h | 4 ++++ iMEGA/Categories/UIImage+MNZCategory.m | 21 ++++++++++++++++++++- iMEGA/Vendor/JSQMessagesViewController | 2 +- iMEGA/Vendor/SDK | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/iMEGA/Categories/UIImage+MNZCategory.h b/iMEGA/Categories/UIImage+MNZCategory.h index 4252ae5508..352f558936 100644 --- a/iMEGA/Categories/UIImage+MNZCategory.h +++ b/iMEGA/Categories/UIImage+MNZCategory.h @@ -8,11 +8,15 @@ typedef NS_ENUM(NSInteger, MEGAChatMessageEndCallReason); + (UIImage *)mnz_convertBitmapRGBA8ToUIImage:(unsigned char *)buffer withWidth:(NSInteger)width withHeight:(NSInteger)height; + + (UIImage *)mnz_imageForUserHandle:(uint64_t)userHandle size:(CGSize)size delegate:(id)delegate; + (UIImage *)mnz_imageForUserHandle:(uint64_t)userHandle name:(NSString *)name size:(CGSize)size delegate:(id)delegate; + + (UIImage *)mnz_qrImageFromString:(NSString *)qrString withSize:(CGSize)size color:(UIColor *)color; + (UIImage *)mnz_qrImageWithDotsFromString:(NSString *)qrString withSize:(CGSize)size color:(UIColor *)color; + (UIImage *)mnz_imageByEndCallReason:(MEGAChatMessageEndCallReason)endCallReason userHandle:(uint64_t)userHandle; ++ (UIImage *)mnz_imageNamed:(NSString *)name scaledToSize:(CGSize)newSize; + @end diff --git a/iMEGA/Categories/UIImage+MNZCategory.m b/iMEGA/Categories/UIImage+MNZCategory.m index d70a59c2cd..c7b10f3653 100644 --- a/iMEGA/Categories/UIImage+MNZCategory.m +++ b/iMEGA/Categories/UIImage+MNZCategory.m @@ -1,13 +1,16 @@ +#import "UIImage+MNZCategory.h" + #import "Helper.h" #import "MEGAStore.h" #import "UIImage+GKContact.h" -#import "UIImage+MNZCategory.h" #import "UIColor+MNZCategory.h" @implementation UIImage (MNZCategory) +#pragma mark - Video calls + + (UIImage *)mnz_convertBitmapRGBA8ToUIImage:(unsigned char *)buffer withWidth:(NSInteger)width withHeight:(NSInteger)height { @@ -92,6 +95,8 @@ + (UIImage *)mnz_convertBitmapRGBA8ToUIImage:(unsigned char *)buffer return image; } +#pragma mark - Avatars + + (UIImage *)mnz_imageForUserHandle:(uint64_t)userHandle size:(CGSize)size delegate:(id)delegate { return [self mnz_imageForUserHandle:userHandle name:@"?" size:size delegate:delegate]; } @@ -293,6 +298,8 @@ + (UIImage *)mnz_qrImageWithDotsFromString:(NSString *)qrString withSize:(CGSize return qrImage; } +#pragma mark - Chat + + (UIImage *)mnz_imageByEndCallReason:(MEGAChatMessageEndCallReason)endCallReason userHandle:(uint64_t)userHandle { UIImage *endCallReasonImage; @@ -333,4 +340,16 @@ + (UIImage *)mnz_imageByEndCallReason:(MEGAChatMessageEndCallReason)endCallReaso return endCallReasonImage; } +#pragma mark - Utils + ++ (UIImage *)mnz_imageNamed:(NSString *)name scaledToSize:(CGSize)newSize { + UIImage *image = [UIImage imageNamed:name]; + + UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); + [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; + UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return scaledImage; +} + @end diff --git a/iMEGA/Vendor/JSQMessagesViewController b/iMEGA/Vendor/JSQMessagesViewController index 280102dace..97dc13a199 160000 --- a/iMEGA/Vendor/JSQMessagesViewController +++ b/iMEGA/Vendor/JSQMessagesViewController @@ -1 +1 @@ -Subproject commit 280102dace589881facc626febc65022ffa1002a +Subproject commit 97dc13a199b99385d16ab33a82bb12d7a98c64b9 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 6c46c5866e..b04c8530ec 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 6c46c5866e628e5630ac15e1f523639da8815c0c +Subproject commit b04c8530ecbb3c1c9b2e75650baf4d1a7ed29cb4 From 4286efe4c9a4be9e4b06667d0c94d36c60850b45 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 10 Oct 2018 17:24:51 +0200 Subject: [PATCH 08/58] Added button background for the empty state of assets picker - Updated submodule --- .../emptyStateButtonGrey.imageset/Contents.json | 12 ++++++++++++ .../outlineButtonGrey.pdf | Bin 0 -> 5861 bytes iMEGA/Vendor/JSQMessagesViewController | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/outlineButtonGrey.pdf diff --git a/iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/Contents.json b/iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/Contents.json new file mode 100644 index 0000000000..ed7a4092fc --- /dev/null +++ b/iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "outlineButtonGrey.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/outlineButtonGrey.pdf b/iMEGA/Images.xcassets/Empty states/emptyStateButtonGrey.imageset/outlineButtonGrey.pdf new file mode 100644 index 0000000000000000000000000000000000000000..926d81dd987cc57e76bb3670c24b11201021a9f9 GIT binary patch literal 5861 zcmbtY2{_bS*tZpiP+2m>Z<7RUB>Q|ziazJ_dZ1Vz^hAEao5T>&z zGtQu3wKuk0eY{;*iO|Qc96k{kM|Hj74A30N72f;-9y}aCS8ZR}!Jp^CeQUFEX2!cN zWh!7TrBZH)=K(E{DjM?W)~%g8pH?wT=c^@9YTOV@|i zlO5iKnzp}j`Nr&sDRi7)_B^q*)i}erMd4!tz&cs}^bx6b=-NYn9zMloV27RGo4F@ z9*5Dxx`0ebMpe{6W*~?%&H;zlyNSYJL5E>hc7}nZe;NQm43TI%EXL(~kD4P4By(i? zZ|jGmBkMmgg7~iWpBRDsSkRGOh@LCj<@*xVaCk=&oJjXW4FC)TQBuNrfJ~qyTjDY@ zAW0}`u79r?b|@#|P#uqRz4?85dLYO(Jj%)WCW_=Gj3;R&dLS6=PzwOj0J-2@NgmV! zS)m-950Uy0u!>&M0Qf=at9HW?bao~h1=bd0d5i+#0f5&B)bWNC#n>C-29{@H>Q1l) zglYv9ji2sWzUZ)GV-T?vETFM;OR8R1L>Hx=#ds#DcT`~FKMnt~bzwh|bCm=kh~nY? zbs}wE5JUy*W{bhJJT5|Oe9*in8-Q56mKkX`$c2k{>it6NWq&@9Yf#ljq@_w_?=-_l| z|Dg4fPk#lO_Fe$hTNgueKX`^A^Wt2c>8aCg4Vx5an8+Cn$!o{0tf^uP&^nW z&YD4+`J88>;f2FS&XjOv>%&C8FBw3iPWCEzT4bg6NHTalE0QY1^dWf6I_owymT*lY znQU@Xpu2nP(Pc6ybCN~-G+%~8n7Ar4`(oHC1sTo#21FH}Az~Y0lSbz8)cQ*}RhS0) z>B;_wwv%65taet4^|sM4;rsPh#zQ?W6W2@V??Ku8Uf>?6l7o#LlWbxa3OE-QjQXZ* zwrd7Qx$fxg%e8pz>aXz46VntU(~L$P=AT0dtYjO!#wllus}uCeN*s@0oE(3STmq4; z9wRFmd9=nA>|h?(@R^>UE@FP(!I?bWUQGC_^lo|p?p;kVVl%02ao0fhDVu`-9~vwY zSk=j=IZw4z+S}9uStL0|QCzTT$|46~^Wy5MgZBW4K* z^z9w@JtM_W&3-4D} z?)7bYhnvnc+-dx>B9GqG27R<89-Jw{o;4ciP)BhphT7#Z0PZgar4YGsg40jpCgli- zEYR-~6XgR+u}c)T7pYz;$`6nM6k!A8Zg=f&lJ)p|y{EbwAa(P&Db@Ab6F2GT{nRf4 zg6p)P_gRLr zw%qz3F2D*Jv%5Qwso#U($M?y70#81q=ti9wAbaCCSR~6yqyDySOvQuc5j9)w(70w9 zt2{<efkIWipW;o9jJpl1K@LppA(oECr2cCQj*YVFqy6^-u zc{Z4$1z+A9P^{A6~TIwZw_m zg82Mc`-HhfZ!q$Hf1-P$g;+Yj#W`QiZ%_BiD0J#B*6I0Y6&YlxjhDQY3(`FnL^_?gCa z?yrP(S}YiAGAS_0ISHIBey4lZyfFFF>3z;pd z2=gl2vHP=rR&F*Pm<+52vIF&jiSQ;#_GG8z*5t|LX?SyqjWOM8D&tAx>DT7agrvpx zYgXXm2t-Cf;X;mRmwfhczFYw&x7g4+>uPc5X|p&n6KTa#(^5TmC8QR_5MuE{XDLHo zC%qs&yGFgn#P?n8d(g)l5sE2wxf$UZHK;UH);Q*mXR-NRLRrRHvQ09WH@Cu9K`bHC+fUi>!Sf|6n=^|(85uoS>wAo-ztD0!%QIzLm`*Oj zFd~(8nAJjLK}1hG0I+f;`btx0(eX z*<`b#Vb;p4mBbB(4W3=TT^O}oh(`#4VROL6O`tN+r}yOC*YUE>mB(mvQ1_#Vo}lEj(^!^ym*IOc>AYv|s6uns#e&Y#1Jp z8{9A36=u|8j8%_lzMm664J6W;hbF$b9((;oY1}4y$!;k}{=9sce3bm@YPafqAIKJJ zpJLx_XLzlDd3vXR7fB^U^_=!AAe%~!%79v)PMr4CF;}YkI`X>80B*YOrWbP#_gwkGa0GSoQyscR%KSmY_slS6+v0V98cVw-Oi?ULJ&cGqw`p&sQQLY@zPY2Qn`}OI6<`2gu$WFfyH5A*s8d|ZYYt*yz5VN+vo7l zNbsk)xltpZ>)XTa8-WXiCmT<8-N)Q$+&>M^RIC{$p^eN3-xrRV6->6TX0Dc2UZ1Zv zK$+aS!Dt(88C@ct`--6X!cM(6XEuA*9~hlHMVR$2T}b`dJk(z^>gd}|9!1$mEziZw zS>@ZZscm1N=-7z*H2%DvIF~tFQDg0E;j6N6X(g7Z9;E)H`E9gouWV1hbguM$skc#a zO)n>IHRJZj{#5opoj;>3kyXA$C36n3^0|47dnt|n!k}lk&$Ai+{q&7D?~TKaGx|RD zg}up^vh}{~Jv!spoqD$Otf)fD!R__Pjilt_ch*|O*iM#5DTs9UJF7}fK`RkqY$4~b zyU16ReEE=CSX4OD=AZb&e5D+pH$>Ro(@)H|E<~8g`lR|07J~Pq`nW379$3uZv7LFn zV#&xh*oWdm^Yt@Y|csgs%MbJMOy#YjkVFvVGTFl7i+)ZZ%@3Z^Log z^6QswciW|>IpHJu)e0N_`x(2K_2$@_dE^!{Aw5dL{Wk61st2E=)zs)_;M(r*N8azp z(8KesxD-tM=+JvuavaJ?b%}^-d@`u-NsF)vuFCh zRw5McvSfnmrJP%HS3h-O-qoJpo;e51)&Yv-8py?Ube_7SBwV;(W1QKAdbI;JfH6ZB{MZGnsEy}YC>7PKVj=*AKrjSQe^1Y6?$*%VoQq2mKutmqs+)IQSmAG{%(}dQk2g2v{GR`al$KD-G z_KnH{i0hpKn+X$oIRmdRMT7-F56^2w7ibP8_U1(!pJ9dSz+B7Oh_)t_lE5x^R-s_9G9OE8q8JA&W z1=qk$9|8Y#3XUxkbzGIPT^gYqdrKF1wWKd+$cxoXpDJOtvlHy1sN7MNGZkjeop{xE z`!SniLaO(-+4oPn!5GgTj4In!ev%3Md5OiBO@G5I}Z$VLM|3gEB5H6AX!Ne-VwvK0?zTj%; z1#r$6vmys~Vn+(@NhyAPQXtcH*Lm9Tvr(5RQigJL{~=jN#DFyAT8m-U#IkMXD0+nc zJgwtWe1BeO&;YX*E@{ygwq`b3rvl}Qj5+n`>?7K%#~5ip`8>E=c_XDR`D@3CwQ->x zp-Cx)bJhIJ`MtAfkl zS)Cy;VxdrKEWN_T;Spi8)?qN0zG+g>!8qo7_iMm6?#Jo3_5UNS9WgniQXcVF$~q(< ze#>h=2%5k0+F@CKRPX=I_c((fSFo10sNY!-Wt=OC1|l&eTDF$XBqryu)F-9MUl4au*ZPmxApw>1;!v0j3`+XIC8fk=jG<6| z()Vx8Uz|W9JuF=@Bzh6{gNcGDlUPL;(mYiT`3l$%5(;t!XZh>zzqt<&Jk|;T1;G#i z=wBB|N>Wln5@ZGXDU%e3lc<-&3*>YpgGwMse*32kjv#SQ|B^wW;v|>`6!;{;T~ Date: Wed, 10 Oct 2018 17:39:39 +0200 Subject: [PATCH 09/58] Minor changes - Removed two deprecation warnings --- iMEGA/Utils/DevicePermissionsHelper.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index ff9e088b1a..b1bc16b339 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -65,7 +65,10 @@ + (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL granted))han } }]; } else { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]]; + #pragma clang diagnostic pop if (handler) { handler(NO); } @@ -96,7 +99,7 @@ + (void)alertPermissionWithMessage:(NSString *)message completionHandler:(void ( if (handler) { handler(); } - [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; }]]; [UIApplication.mnz_visibleViewController presentViewController:permissionsAlertController animated:YES completion:nil]; From fe6bb4601c617250127753b8109f719275882988 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Thu, 11 Oct 2018 13:38:24 +0200 Subject: [PATCH 10/58] Show a different text when asking for permissions when receiving an incoming call - The title of the modal is now "Incoming call", so the user knows what is happening (why the permission is being requested). --- iMEGA/AppDelegate.m | 2 +- iMEGA/Chat/MessagesViewController.m | 2 +- iMEGA/Languages/Base.lproj/Localizable.strings | 2 ++ iMEGA/Login/MainTabBarController.m | 2 +- iMEGA/Login/OnboardingViewController.swift | 2 +- iMEGA/Utils/DevicePermissionsHelper.h | 2 +- iMEGA/Utils/DevicePermissionsHelper.m | 8 ++++---- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index 8fdfa69b4b..cf9a84be6d 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -575,7 +575,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct MEGAChatConnection chatConnection = [[MEGASdkManager sharedMEGAChatSdk] chatConnectionState:self.chatRoom.chatId]; MEGALogDebug(@"Chat %@ connection state: %ld", [MEGASdk base64HandleForUserHandle:self.chatRoom.chatId], (long)chatConnection); if (chatConnection == MEGAChatConnectionOnline) { - [DevicePermissionsHelper audioPermissionModal:YES withCompletionHandler:^(BOOL granted) { + [DevicePermissionsHelper audioPermissionModal:YES forIncomingCall:NO withCompletionHandler:^(BOOL granted) { if (granted) { if (self.videoCall) { [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index 1f0cdd569e..fdc458ef21 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -458,7 +458,7 @@ - (void)rightBarButtonItems { } - (void)startAudioVideoCall:(UIBarButtonItem *)sender { - [DevicePermissionsHelper audioPermissionModal:YES withCompletionHandler:^(BOOL granted) { + [DevicePermissionsHelper audioPermissionModal:YES forIncomingCall:NO withCompletionHandler:^(BOOL granted) { if (granted) { if (sender.tag) { [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index d08bcf7478..36bbe50751 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -1630,6 +1630,8 @@ "To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; +/* notification subtitle of incoming calls */ +"Incoming call"="Incoming call"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"; /* Title label that explains that the user is going to be asked for the notifications permission */ diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index 81fffabe06..4272ec2c6c 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -270,7 +270,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { case MEGAChatCallStatusRingIn: { [self.missedCallsDictionary setObject:call forKey:@(call.chatId)]; - [DevicePermissionsHelper audioPermissionModal:YES withCompletionHandler:^(BOOL granted) { + [DevicePermissionsHelper audioPermissionModal:YES forIncomingCall:YES withCompletionHandler:^(BOOL granted) { if (granted) { if (call.hasRemoteVideo) { [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { diff --git a/iMEGA/Login/OnboardingViewController.swift b/iMEGA/Login/OnboardingViewController.swift index 2949d5929b..63a14be094 100644 --- a/iMEGA/Login/OnboardingViewController.swift +++ b/iMEGA/Login/OnboardingViewController.swift @@ -251,7 +251,7 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { } case .microphoneAndCameraPermissions: - DevicePermissionsHelper.audioPermissionModal(false) { (_) in + DevicePermissionsHelper.audioPermissionModal(false, forIncomingCall: false) { (_) in DevicePermissionsHelper.videoPermission { (_) in self.nextPageOrDismiss() } diff --git a/iMEGA/Utils/DevicePermissionsHelper.h b/iMEGA/Utils/DevicePermissionsHelper.h index 35e046890a..147f6cace7 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.h +++ b/iMEGA/Utils/DevicePermissionsHelper.h @@ -3,7 +3,7 @@ @interface DevicePermissionsHelper : NSObject -+ (void)audioPermissionModal:(BOOL)modal withCompletionHandler:(void (^)(BOOL granted))handler; ++ (void)audioPermissionModal:(BOOL)modal forIncomingCall:(BOOL)incomingCall withCompletionHandler:(void (^)(BOOL granted))handler; + (void)videoPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; + (void)photosPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; + (void)notificationsPermissionWithCompletionHandler:(void (^)(BOOL granted))handler; diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index b1bc16b339..4ca062def9 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -13,9 +13,9 @@ @implementation DevicePermissionsHelper #pragma mark - Permissions requests -+ (void)audioPermissionModal:(BOOL)modal withCompletionHandler:(void (^)(BOOL granted))handler { ++ (void)audioPermissionModal:(BOOL)modal forIncomingCall:(BOOL)incomingCall withCompletionHandler:(void (^)(BOOL granted))handler { if (modal && [self shouldAskForAudioPermissions]) { - [self modalAudioPermissionWithCompletionHandler:handler]; + [self modalAudioPermissionForIncomingCall:incomingCall withCompletionHandler:handler]; } else { [self audioPermissionWithCompletionHandler:handler]; } @@ -109,12 +109,12 @@ + (void)alertPermissionWithMessage:(NSString *)message completionHandler:(void ( #pragma mark - Modals -+ (void)modalAudioPermissionWithCompletionHandler:(void (^)(BOOL granted))handler { ++ (void)modalAudioPermissionForIncomingCall:(BOOL)incomingCall withCompletionHandler:(void (^)(BOOL granted))handler { CustomModalAlertViewController *permissionsModal = [self permissionsModal]; __weak CustomModalAlertViewController *weakPermissionsModal = permissionsModal; permissionsModal.image = [UIImage imageNamed:@"groupChat"]; - permissionsModal.viewTitle = AMLocalizedString(@"Enable Microphone and Camera", @"Title label that explains that the user is going to be asked for the microphone and camera permission"); + permissionsModal.viewTitle = incomingCall ? AMLocalizedString(@"Incoming call", nil) : AMLocalizedString(@"Enable Microphone and Camera", @"Title label that explains that the user is going to be asked for the microphone and camera permission"); permissionsModal.detail = AMLocalizedString(@"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", @"Detailed explanation of why the user should give permission to access to the camera and the microphone"); permissionsModal.action = AMLocalizedString(@"Enable Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand"); permissionsModal.dismiss = AMLocalizedString(@"notNow", nil); From 5f764b261a92dd75173aacf46abd47c87cf575e0 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Thu, 29 Nov 2018 12:11:05 +0100 Subject: [PATCH 11/58] Improved management of storage over-quota errors (10938) If the app receives an error type overquota or goingOverquota the app should't show a HUD with the error, the account upgrade alert will be presented if proceed. The account upgrade view controller isn't shown more than once a week. --- iMEGA/AppDelegate.m | 158 ++++++++++-------- iMEGA/Cloud drive/BrowserViewController.m | 8 +- iMEGA/Cloud drive/CloudDriveViewController.m | 47 ++---- .../Languages/Base.lproj/Localizable.strings | 4 + .../CustomModalAlertViewController.h | 1 + .../CustomModalAlertViewController.m | 16 +- 6 files changed, 121 insertions(+), 113 deletions(-) diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index 565fee8b1f..8f052b0d3a 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -36,6 +36,7 @@ #import "UIImage+GKContact.h" #import "UITextField+MNZCategory.h" +#import "AchievementsViewController.h" #import "BrowserViewController.h" #import "CallViewController.h" #import "CameraUploadsPopUpViewController.h" @@ -57,6 +58,7 @@ #import "MEGAPhotoBrowserViewController.h" #import "MessagesViewController.h" #import "MyAccountHallViewController.h" +#import "ProductDetailViewController.h" #import "SettingsTableViewController.h" #import "SharedItemsViewController.h" #import "TwoFactorAuthenticationViewController.h" @@ -82,8 +84,6 @@ @interface AppDelegate () 0) { + UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; + MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; + + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; + } else { + // Redirect to my account if the products are not available + [self.mainTBC setSelectedIndex:4]; + } + }]; + }; + + customModalAlertVC.onDismiss = ^{ + [weakCustom dismissViewControllerAnimated:YES completion:^{ + self.upgradeVCPresented = NO; + }]; + }; + + customModalAlertVC.onBonus = ^{ + [weakCustom dismissViewControllerAnimated:YES completion:^{ + self.upgradeVCPresented = NO; + AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; + achievementsVC.enableCloseBarButton = YES; + UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:achievementsVC]; + [UIApplication.mnz_presentingViewController presentViewController:navigation animated:YES completion:nil]; + }]; + }; + + self.upgradeVCPresented = YES; + [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; + } +} #pragma mark - Battery changed @@ -1916,6 +1954,24 @@ - (void)onEvent:(MEGASdk *)api event:(MEGAEvent *)event { _messageForSuspendedAccount = event.text; break; + case EventStorage: { + [api getAccountDetails]; + static BOOL alreadyPresented = NO; + if (!alreadyPresented && (event.number == StorageStateRed || event.number == StorageStateOrange)) { + NSString *detail = event.number == StorageStateOrange ? AMLocalizedString(@"cloudDriveIsAlmostFull", @"Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are.") : AMLocalizedString(@"cloudDriveIsFull", @"A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text."); + detail = [detail mnz_removeWebclientFormatters]; + NSString *maxStorage = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7]]; + NSString *maxStorageTB = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7] / 1024]; + detail = [detail stringByReplacingOccurrencesOfString:@"4096" withString:maxStorage]; + detail = [detail stringByReplacingOccurrencesOfString:@"4" withString:maxStorageTB]; + alreadyPresented = YES; + NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); + UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + [self presentUpgradeViewControllerTitle:title detail:detail image:image]; + } + } + break; + default: break; } @@ -2040,7 +2096,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEgoingOverquota: case MEGAErrorTypeApiEOverQuota: { - [self showOverquotaAlert]; + NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); + NSString *detail = AMLocalizedString(@"This action can not be completed as it would take you over your current storage limit", @"Error message shown to user when a copy/import operation would take them over their storage limit."); + UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + [self presentUpgradeViewControllerTitle:title detail:detail image:image]; + break; } @@ -2170,7 +2230,6 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } }); - isOverquota = NO; [[MEGASdkManager sharedMEGASdk] getAccountDetails]; [self copyDatabasesForExtensions]; [[NSUserDefaults standardUserDefaults] setBool:[api appleVoipPushEnabled] forKey:@"VoIP_messages"]; @@ -2318,32 +2377,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG break; } - case MEGARequestTypeAccountDetails: { - + case MEGARequestTypeAccountDetails: [[MEGASdkManager sharedMEGASdk] mnz_setAccountDetails:[request megaAccountDetails]]; - - if (isOverquota) { - NSString *overquotaMessage = [[request megaAccountDetails] type] > MEGAAccountTypeFree ? AMLocalizedString(@"quotaExceeded", nil) : AMLocalizedString(@"overquotaAlert_message", nil); - self.overquotaAlertView = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"overquotaAlert_title", nil) message:overquotaMessage preferredStyle:UIAlertControllerStyleAlert]; - [self.overquotaAlertView addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [self.overquotaAlertView addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; - - if (self.window.rootViewController.presentedViewController) { - [self.window.rootViewController dismissViewControllerAnimated:YES completion:^{ - [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; - }]; - } else { - [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; - } - }]]; - [UIApplication.mnz_presentingViewController presentViewController:self.overquotaAlertView animated:YES completion:nil]; - isOverquota = NO; - } - break; - } case MEGARequestTypeGetAttrUser: { MEGAUser *user = (request.email == nil) ? [[MEGASdkManager sharedMEGASdk] myUser] : [api contactForEmail:request.email]; @@ -2517,35 +2553,21 @@ - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { - if (error.type == MEGAErrorTypeApiEOverQuota && error.value) { + MEGALogDebug(@"onTransferTemporaryError %td", error.type) + if (error.type == MEGAErrorTypeApiEOverQuota || error.type == MEGAErrorTypeApiEgoingOverquota) { [SVProgressHUD dismiss]; - CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; - customModalAlertVC.modalPresentationStyle = UIModalPresentationOverCurrentContext; - customModalAlertVC.image = [UIImage imageNamed:@"transfer-quota-empty"]; - customModalAlertVC.viewTitle = AMLocalizedString(@"depletedTransferQuota_title", @"Title shown when you almost had used your available transfer quota."); - customModalAlertVC.detail = AMLocalizedString(@"depletedTransferQuota_message", @"Description shown when you almost had used your available transfer quota."); - customModalAlertVC.action = AMLocalizedString(@"seePlans", @"Button title to see the available pro plans in MEGA"); - customModalAlertVC.dismiss = AMLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); - if ([[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { - customModalAlertVC.bonus = AMLocalizedString(@"getBonus", @"Button title to see the available bonus"); + if (error.value) { // Bandwidth overquota error + NSString *title = AMLocalizedString(@"depletedTransferQuota_title", @"Title shown when you almost had used your available transfer quota."); + NSString *detail = AMLocalizedString(@"depletedTransferQuota_message", @"Description shown when you almost had used your available transfer quota."); + UIImage *image = [UIImage imageNamed:@"transfer-quota-empty"]; + [self presentUpgradeViewControllerTitle:title detail:detail image:image]; + } else { // Storage overquota error + NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); + NSString *detail = AMLocalizedString(@"Your upload(s) cannot proceed because your account is full", @"uploads over storage quota warning dialog title"); + UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + [self presentUpgradeViewControllerTitle:title detail:detail image:image]; } - __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; - customModalAlertVC.completion = ^{ - [weakCustom dismissViewControllerAnimated:YES completion:^{ - if ([MEGAPurchase sharedInstance].products.count > 0) { - UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; - - [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; - } else { - // Redirect to my account if the products are not available - [self.mainTBC setSelectedIndex:4]; - } - }]; - }; - - [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; } } @@ -2591,12 +2613,6 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: if (error.type) { switch (error.type) { - case MEGAErrorTypeApiEgoingOverquota: - case MEGAErrorTypeApiEOverQuota: { - [self showOverquotaAlert]; - break; - } - default:{ if (error.type != MEGAErrorTypeApiESid && error.type != MEGAErrorTypeApiESSL && error.type != MEGAErrorTypeApiEExist && error.type != MEGAErrorTypeApiEIncomplete) { NSString *transferFailed = AMLocalizedString(@"Transfer failed:", @"Notification message shown when a transfer failed. Keep colon."); diff --git a/iMEGA/Cloud drive/BrowserViewController.m b/iMEGA/Cloud drive/BrowserViewController.m index 6a1c274b50..4b16ab83a6 100644 --- a/iMEGA/Cloud drive/BrowserViewController.m +++ b/iMEGA/Cloud drive/BrowserViewController.m @@ -860,8 +860,12 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { if (request.type == MEGARequestTypeCopy) { - [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:error.name]; + if (error.type != MEGAErrorTypeApiEOverQuota || error.type != MEGAErrorTypeApiEgoingOverquota) { + [SVProgressHUD dismiss]; + } else { + [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; + [SVProgressHUD showErrorWithStatus:error.name]; + } } return; } diff --git a/iMEGA/Cloud drive/CloudDriveViewController.m b/iMEGA/Cloud drive/CloudDriveViewController.m index bb120b4fd2..6b34e5c6b1 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController.m +++ b/iMEGA/Cloud drive/CloudDriveViewController.m @@ -1083,41 +1083,20 @@ - (void)encourageToUpgrade { static BOOL alreadyPresented = NO; if (!alreadyPresented && ![[MEGASdkManager sharedMEGASdk] mnz_isProAccount]) { - MEGAAccountDetails *accountDetails = [[MEGASdkManager sharedMEGASdk] mnz_accountDetails]; - double percentage = accountDetails.storageUsed.doubleValue / accountDetails.storageMax.doubleValue; - if (accountDetails && percentage > 0.95) { // +95% used storage - NSString *alertMessage = percentage < 1 ? AMLocalizedString(@"cloudDriveIsAlmostFull", @"Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are.") : AMLocalizedString(@"cloudDriveIsFull", @"A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text."); - alertMessage = [alertMessage mnz_removeWebclientFormatters]; - NSString *maxStorage = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7]]; - NSString *maxStorageTB = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7] / 1024]; - alertMessage = [alertMessage stringByReplacingOccurrencesOfString:@"4096" withString:maxStorage]; - alertMessage = [alertMessage stringByReplacingOccurrencesOfString:@"4" withString:maxStorageTB]; - - CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; - customModalAlertVC.modalPresentationStyle = UIModalPresentationOverCurrentContext; - customModalAlertVC.image = [UIImage imageNamed:@"storage_almost_full"]; - customModalAlertVC.viewTitle = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); - customModalAlertVC.detail = alertMessage; - customModalAlertVC.action = AMLocalizedString(@"seePlans", @"Button title to see the available pro plans in MEGA"); - if ([[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { - customModalAlertVC.bonus = AMLocalizedString(@"getBonus", @"Button title to see the available bonus"); + NSDate *lastEncourageDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"encourageUpgradeDate"]; + if (lastEncourageDate) { + NSInteger week = [[NSCalendar currentCalendar] components:NSCalendarUnitWeekOfYear + fromDate:lastEncourageDate + toDate:[NSDate date] + options:NSCalendarWrapComponents].weekOfYear; + if (week < 1) { + return; } - customModalAlertVC.dismiss = AMLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); - __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; - customModalAlertVC.completion = ^{ - [weakCustom dismissViewControllerAnimated:YES completion:^{ - [self showUpgradeTVC]; - }]; - }; - - [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; - + } + MEGAAccountDetails *accountDetails = [[MEGASdkManager sharedMEGASdk] mnz_accountDetails]; + if (accountDetails && (arc4random_uniform(20) == 0)) { // 5 % of the times + [self showUpgradeTVC]; alreadyPresented = YES; - } else { - if (accountDetails && (arc4random_uniform(20) == 0)) { // 5 % of the times - [self showUpgradeTVC]; - alreadyPresented = YES; - } } } } @@ -1126,7 +1105,7 @@ - (void)showUpgradeTVC { if ([MEGAPurchase sharedInstance].products.count > 0) { UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; - + [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"encourageUpgradeDate"]; [self presentViewController:navigationController animated:YES completion:nil]; } } diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index f1dbf9f197..c186bf8bd4 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -1766,3 +1766,7 @@ "Your publicly shared %1 (%2) has been taken down."="Your publicly shared %1 (%2) has been taken down."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Your taken down %1 (%2) has been reinstated."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="This action can not be completed as it would take you over your current storage limit"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; diff --git a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h index 47a1165d63..40f9b11b63 100644 --- a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h +++ b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h @@ -5,6 +5,7 @@ @property (nonatomic, strong) void (^completion)(void); @property (nonatomic, strong) void (^onDismiss)(void); +@property (nonatomic, strong) void (^onBonus)(void); @property (nonatomic) UIImage *image; @property (getter=shouldRoundImage) BOOL roundImage; diff --git a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m index 5b7b7fc8d2..607f1529a5 100644 --- a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m +++ b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m @@ -113,12 +113,16 @@ - (IBAction)dismissTouchUpInside:(UIButton *)sender { - (IBAction)bonusTouchUpInside:(UIButton *)sender { [self fadeOutBackgroundCompletion:^ { - [self dismissViewControllerAnimated:YES completion:^{ - AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; - achievementsVC.enableCloseBarButton = YES; - UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:achievementsVC]; - [UIApplication.mnz_presentingViewController presentViewController:navigation animated:YES completion:nil]; - }]; + if (self.onBonus) { + self.onBonus(); + } else { + [self dismissViewControllerAnimated:YES completion:^{ + AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; + achievementsVC.enableCloseBarButton = YES; + UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:achievementsVC]; + [UIApplication.mnz_presentingViewController presentViewController:navigation animated:YES completion:nil]; + }]; + } }]; } From 0dc60c77798421e2994e2ac755d9d8390e2e2a69 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Thu, 29 Nov 2018 12:34:41 +0100 Subject: [PATCH 12/58] Add storage full image --- iMEGA/AppDelegate.m | 6 ++--- .../storage_full.imageset/Contents.json | 23 ++++++++++++++++++ .../storage_full.imageset/storage_full.png | Bin 0 -> 5194 bytes .../storage_full.imageset/storage_full@2x.png | Bin 0 -> 10761 bytes .../storage_full.imageset/storage_full@3x.png | Bin 0 -> 16673 bytes 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full.png create mode 100644 iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@2x.png create mode 100644 iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@3x.png diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index 8f052b0d3a..692047e02c 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -1966,7 +1966,7 @@ - (void)onEvent:(MEGASdk *)api event:(MEGAEvent *)event { detail = [detail stringByReplacingOccurrencesOfString:@"4" withString:maxStorageTB]; alreadyPresented = YES; NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); - UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + UIImage *image = event.number == StorageStateOrange ? [UIImage imageNamed:@"storage_almost_full"] : [UIImage imageNamed:@"storage_full"]; [self presentUpgradeViewControllerTitle:title detail:detail image:image]; } } @@ -2098,7 +2098,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEOverQuota: { NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); NSString *detail = AMLocalizedString(@"This action can not be completed as it would take you over your current storage limit", @"Error message shown to user when a copy/import operation would take them over their storage limit."); - UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + UIImage *image = [api mnz_accountDetails].storageMax.longLongValue > [api mnz_accountDetails].storageUsed.longLongValue ? [UIImage imageNamed:@"storage_almost_full"] : [UIImage imageNamed:@"storage_full"]; [self presentUpgradeViewControllerTitle:title detail:detail image:image]; break; @@ -2565,7 +2565,7 @@ - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfe } else { // Storage overquota error NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); NSString *detail = AMLocalizedString(@"Your upload(s) cannot proceed because your account is full", @"uploads over storage quota warning dialog title"); - UIImage *image = [UIImage imageNamed:@"storage_almost_full"]; + UIImage *image = [api mnz_accountDetails].storageMax.longLongValue > [api mnz_accountDetails].storageUsed.longLongValue ? [UIImage imageNamed:@"storage_almost_full"] : [UIImage imageNamed:@"storage_full"]; [self presentUpgradeViewControllerTitle:title detail:detail image:image]; } } diff --git a/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/Contents.json b/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/Contents.json new file mode 100644 index 0000000000..7dc11c58ec --- /dev/null +++ b/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "storage_full.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "storage_full@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "storage_full@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full.png b/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full.png new file mode 100644 index 0000000000000000000000000000000000000000..5934eb8aa9d7564dcc1fb12f613cd939e1b69a5a GIT binary patch literal 5194 zcmXAtc{J4D|HtpFh8bg&n8q?Q5sFY`4TCHdHIh_PBeE4nDf6+8A!HkskF=Vov|3WB zkoVYCma-NymV_*$$ri)c=l8qk+-$DFA@<9*5lr*KYQI z8-rdut1Ub~0w5>5XSdZM7Hs0%tE}Vp#F~)p>OMZNPtxw)=o>jYyF>IoAzQ`j@w$>b zq)q&_h>uTp2&(JQ&$9e?J?6Vie_q#~?yOu&J{&q&7`7B_sqz}aoK3V5B6nfQ&~@Rq zcBy`O-6bCDmWF_EHc_@4USFNOzfH2gW{e(-5XwcuceX*w0~6dC$z3Q39z1^@?pHp` zL`)=JA8uF-k<;Zzzkzkm!yidZcUCzu3GV|I1Orx)D2bUDw;0==`CWJ}Mumq_&nC+7 z(7Ph{ZGHbi=?tf=M$#z@+t}GeAcZzrj0I`*5TO4CF-v7f443^);olo`$djR zdAIg%ScP9e4IVK2Cs?S%8%Pvk@wwW_rOpM^MW}8H1C>1$xIVI)@c;RFb^D=(d4}rz za9$jyY{Lh#a$!}+9YuE7FHg%uE{Q++H_VqSHtv@2jOT5LIn^!m%S>x6X`=7&1+r23 z_9}A}DcZn;A@|mXh1Qu)1?9iurYV67!q2gGi)U(z_imNFlx!7GbEYHvtxhhX1$_bC z(yk4ryJUh=_YciKIOxnIe5NK@9zN_T^DL+kl=tA~Q(SUP%ZU|m?{eKM$s8{S9IPf# z2+js^LAeZy95`V%kkn~wy4`~AcWB44(V?5}k#O+7qRp>j&x_fY z^=g`E)JHuO8{`scO{5Kv)VaYY7bC};24+X+y2+ZvI~6UVxQm#%K!)YIFPk0>`kwH2 z1m8XI{Z?5svyT<3MWb_XJV)$^6=_L)DXV_;V_D%gq4{y=eo*c%(=tpxnG+ttiU^H3 zWEcQ_{As^&t>z>`O<;x*$CnXhj_B_1o>_vm|6j=>YF*OCI81C%T%9eGXR$UA*Rk_C z^%Caq|M}6Ae?q1uuE$F^^+5@zmD*hzJL}LY+a0s}WsD{B!uK_yF@=}g$h+Rplq=+0 zL@xX;t{>XAZVeS)Fd`k~Zqlqoorp^_L3D4_w@A9CTmRFgrTXLNNowPl8q0~&*vm1G zh|dVgOjqPzbxk;i%kaGm%FB?^ml}Sqm{aONuYeVS;g z*dXMRVYW_prEZZbu~?O`s<`O(M$H~@X~XAFa%Juf)yu4E?DXZU*3ayoS+!hHUUx-o zTXh>#^~v!+XZiot?Kytpnx<~atHZsfvw=-b%>m6ZSO+X54m<(}0bT=s#u@Qy$42<6=baQMNa=!dhS5yik&j`>U7-mfkL+fE{b`}Qu}>f*b*WZ-JoXs;LQW3D6%HDf9~Q6dIU;r(_if6zOfS8N%6)pB zVvF43dO2K;tc8j9**dg?=}EWLVLaWO<+pd=kiyHAjiyW^~WNlvhM z>i4B5GheLb8ng5SCVr^O!&t(uJm}_~V^#gd8xDSVGRJeo zOLV`LeLe)eqcj8#!okM0IKBJ8|E2ia#dEoENXaIqLT zODhZVo7GCa7{k{JRJQiHE~z9la6?~c6r8IXP~!4*;vIC|Q6VX(p1!*K$`AXc_*c)O zBdQ$gmOoJ_lKl??bI%U<3k{z=)N$5DT0`!Yf2qcmU*@PBy*Nh0zU;%e8gw=yLKd@g z0=}7>(*YBP$5r=tNh2WAcD*g&2HM86hpEsU-Tt98I3aVd5nL8&Hg&A}JoAvgP{T;7fcZlD;SmjJc%AbA-Jn@7x=hxLt*Cq5t@w!tIrW z7U!5Q!-{(*R)jSPBiboXSP`t4_@6DyU9wdr`D@PM7TrHeP15l=n@K<}%?Ujyi-y9- zQjl{`I|59NSDh{tB?0R&p z6FPD92YX;Ei12ca__HsO^g~~qk*R?k?7U#yrco0pGRv+o-DF&3G}Z@;I(u7IQ`=Nu z-Ktl|fP`7zc1z!wqz*2w2gNh1+|7%NEpC)|$=11A zH^W27z75WD%w#W$?5k_12_}=3|sVpJ{v@Cx3)N4QZV#!)UAI1(Bx6GKC z!37)3qHIk^#(4h79rc~W&WJ>YcwxIZ8uA9p@K#rl-3`u_>(9xs(^_)w73@pnp^)a~ zcggvT=q0-{&NW5;7zmYwwxXFNo}(Y=sQuZETnS%Q(Vns{EHL0CG#g~q%3PJpChrB( z1{^qqg$n}?1EoB8QSlimM6SVK`3aZ3<+%jhSJ#A z+aw0nEUYl$`BGN1OMqjH=)pqw{viAUHW%4}Tq|%+j);WF3^ZV(z=tgV*a1DvkYBQ_ zmFnm~f7=Ur`|=-u8D46j6VJ{7?QK9n+yLApX>|#KAJHR=1P#f|(;=pWiC0|Svw2GM zK4u#Jrd77i(j!#`)njEzjeXG| z=ogs7fK&uDec~<&`t}ESkD`wJRZ;~+EW6)aYULSB2>}XcRY0Z-sP+T(GQ7zx4Da;~ znquHbf|7iM6QEUI z3nllwsh^U652)2T@IMoHQwK&CAL1NP*kGLn*_o05KGI+Bft z8LuYZDS=+~p{tl1JLy(844r*^zVE-0C7h!_wAq7CIB$GfE(=xrIA%UA)VsE8&YaH5 z`je>AduzErwZ2iU6A)j}vaUyx3}a{f<$KH$ad#JTY8!<;1mz`+{ecl2_{p3DKI_3T$~4E>!yFpgb5lR$-} zfFXIE#n*BrDWIbV2S?%TQEtlB?`Og`<2ug$fa$@&<;j5J`_8uy8Y?+(Ke-9ZLo+1= zk`6apzPwjyCTKIwx*y}|6wL^8G)=0fS}kwh%zWGqL5uZLVM|kOMoLViuVtD#pR`*J zeQT}WYJv4LsD!c6Lr1Z*TAn~~dda;i0|p+T8$N77Rd2888mI5`PNtvbB9*>|+Wk0X z+tzVx`Id}{%d&3#dA`ZO%|j{w>V?NrGJGWBs8Owx&QBHTF-k6nn+OC$nK)c+jx5-{ z`VkYp6Va=^y8%983MsRlyqp#9ci=!nZgCHpZZ?6{v-VC|{g=6al2T=|cT^%|@3r## zr(Q#^Oqz!nJu^8?zHQV=`&LAem_PPXb4{6***Ud8AqGZJR6t*#t=DmRNh>9fiJ$JY;#U%-w0O6FH11hCfKmflG#zJg*&cFLud_?fN91o!Gh7!-!m z8gW;MiIIxY-425Y<=>IuehWNN0zxp9#jT2uM29@@NrB^9?MUF*)ee_{LkAGjUX_O8 zlRa`1M@C=J;6pnJnnxKLfCVUupf}IbiqYc(n&I=x|Dq^6v6&IUk|JA1MFDrcc%J&m zLmwKmer$miNc5FS@?kfv#RPg6`R%8rdvavN1vK~;X;HKV&87+_b9;?|3cEJ4air(a zO45qE;DIz~)`zdf9NHpVzyg(*wEp^N%$>y0^U}ORQ~}{{M<$BrNtluas{usN7{_ih zvRy6Q#4a>I8Os|U6&!W3!T^rZcc?^c4g|h1`oYYX>0Uim(e}Bk13imna5nYL{?V2^isn0ysp_1UdoC! za{M11tF%jJv019BhI%eI+)Yi1W2F>S`L;y3e>}+u0h4t&$(rS!H`+en8TVq4+l7;qp%A#<&yb(=vNPqE-A;3M8NFHWN`lh}5x3Z-!<2CR3WeEw3VEg9z8aqSTatFigvFDA9WMn^bf^UMj%b`h(~f`UE8`!TeYE^x2(WB- zw&+s@MQ2PPFVT@u8yqIj?n6xs)!ute=n7?W5ofA?cl}FXqrj%_tH##}Irc9t(k5%HI8 z6GzpDyrs9TsHxjfkob7Ml{2 z;7Wu$%hRsh^fVoxDM-D(f}hP!_aU5y8Mni)6~nrOx-ZUjvm5MrPPI0?L6lIGxd(`wE{$mkb+BkI|E4g%m6ElweKDb3^K#x=e|42{VpAjG;FK8xr UDP83M_ge_|*zVt5V9ntC56JRo(*OVf literal 0 HcmV?d00001 diff --git a/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@2x.png b/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..82acab425f1b6b6f35ebf98de118109e6e374f69 GIT binary patch literal 10761 zcmW++c|26_7k=-Z8O9)EUo&GXN(@R!W^7qf)}n8+m8G(kifnW3TcHw4WumkSC6q9h zP!cU9>tx?zY#AEnH~s#Y&)m=FzUMvXJ?A;+dFIYdu{&%bh?Btq00gZp%^U!LxR(&X z@^HUg&p-A7K&{it%-AUmn(c;8owt`tS`f9v#HRuZ1)@O1i&1^pv zg>;1jQYULYt_Uao@vRQ4zY*mujXC8(g!g8J@cBmC|1uUhon>b#i~6)jmUdOf+ER%} z=zDlPn!>`jyx#v*{`u6c+{n9PZVAlrYEGT?PJQ%HLb95{V`2g^ zORorEH3j+^M>w6YlYR+Sk{AghNfJQ#z?O@-vO=58IFEvKxdyw2TM&MNW71mq{_>^S zxFI4|r#y3F1ONR`$}YEUQq_cGyFEPvZ`5-%uoAc5-Af=?vu}dd$rF||+*049Lfv}b zRmfNLh%zHqLE`FI;138cWJ|b_45)(#xdHE0la%zOlQQ1v+s}h;5XTwBP zvogW#a8R_!Rq+FLGAB;f8T?HVEhwEGUqhZDIu!X~BBw^Ro0zLudY89pY7cnj%#M4%by!oQmg7Uu4y$2R@ zsvPB9QKV|Oud;aeTTMag8y5QyI#x}NE$}K(v9%&7uRuXcXdCriSUG)$1x<Q zK(7kt%kT$v$05z=-~ioLBfRhJhD;swlTKB*@3Mp32~_8nHRqHd{^X$S=*u|#zP*N6 zLmoqPANg(~V_(@*9*^CmTX(?s^Zly6WqHCNYVlLeLOTMM~6J;FHbX zp7x`R?aF@lbv2Yoym%=LZ>Ysk@LS)E|M6aJ5705)d^pUjx5A&?5kHh}&pM*^=l>hT z{5p|FPJXxtcn7VONOF1~8A%RlL`B|pKLX^@@+ed6*F#7Uijyka^iBqux=AIU6LMdB z!^%G0ks!Hq6}0Wk8sr6Rp=q8>rjxx)1t~gf znwY&Cmy3)f36_MM6BoA7TYHNT)l8`#Q+g#7HF!y`L+f2R5AuP!CueB?O3tI;g%Ezt zFaS1*O#HGf5+chGu6oPkab|G{pP66GjvSa6ZrQ#+v*rfd>q0RUBsQD}VIe(<_zEak z*-|k$N|0i+dG=#lkVO7LLZ%_W4@&V$ps3hjbo@^E$9Hfco8zTHCeJU0)Vi~qU#3}^ z%boD0<1y~o$qkT!_({baJOj36Qy7?~c#T!q8@c9Qj z%qJjH0@Os8hHC|zlolisx5szVqeN~BB@h~i7&+_H{{V@pV$hl>Ri|Z@EU5yNQ~=i zEM4(hA(ObQ9A<*olVB+^hR~bBk{7&z^2WjfZ@9}C??e=7Whq(c)OLsM{*yyDitJnE zxN$x2zY!~JDX)4yat*LFH{3L~a}x&JY#=O~a1DJS3PQodT0jBokD86`ye!8M4m&p6 zcL?g?`MiM{<^7kjhZ7<#J$fYJdLqJ&k5vhAwo&F$bJMB7a{XTRN1^U0VzgQKvq=G23H0>AjFmgen`!zZxfM91AL9ccUX^ zRcrZ53+Wld503Is8agCxL!Yr-BT2vWuytQ3GO=O!&ZBhG&YQwv2h{yef)^ESnzM@$ zO_KHtZaS5M3mGG4*AS0<`fIIp`F zBZ{KAsEnc*<}*DlDqvN!|TWfPWi>+0trXLX)`*=r?0 zwU|t-zyu%2;dm@~tAfF<{5JBt#MX}YlCEr@m(;i3XwfST%K3xJXp|vZ8IH98au|b1 zzeNhD#q)lC0Q$ES)mcQ>Z>Q?6#juR0HVPsdN-v(0yf1~|SxWT1&{7oWfddzLkTwoE z!dHB}`I`AJZ=dm~-W=~RHy@wzfy|$xIyw<2eSCcOaX(V>R!VzJSFSXE7ZF?@FQ zaB0R?p1%#3g98WP$`=y-7i5wHftC%i0_8UIE#S;$T(akDD;;|KDSB}+W}^2&g5#StG|fAxH_-!Q2vHuNIV40$(8NUB;XrBGkjmPZx~y@@ z$E)uPqbL1J(xtHD>s<`jTI{_MZdPC>~qpr$=v*gnKoJ-4(pv%$2lo7N&# z27-=?I3HUg{btm(a)M7#v=wm+#Dh#K(0M6RSW^DRAsL`|my;&|9@=ia&x>Z;=c_MG zSVRSULoJ@)Mv$iA3}9&&8}Ii6q2@Oj|w% zo-$`@zX}7FZCdL(O9zuT3oeqWHIjuDy1uyFKHb=;b-k*tN@Q}!9`#>s7m-sA4A*M- zPnjttOKynGhknp%a4B90+H9uXiAMY+5RngX?xK68%{I`+jy)A>LsNz<3+lqA zHFwXqxW6?xoke)Ezs>9$uTwPMlIZdLnD9$2sai3L`G2_NQ;=#5zgc7Y=2LI=&E$-Y zs&P$&^%2eEc`N9u(7;jY+D{6Z6r?;>+s86mf?MW=CkV^MP5?Y+cy*rj28_Hh@r|`{ z=L1{7M6Wk9xlt?#Tcu&-9*y_A&$*F(okA}TqWe5&7kR9CLqk~*a&s7Dp7@&V`B5osRZ>oaawIPd@^3gYSlx5#>$t#=7IsSL`N!@lY}|) zEw+7O!UnD*jevf4v@~4~?QM62arX1d?^NeZ^Lu<%f>*s1nQ zdpb%8Mh;(l$YD=v`bV?9d$(thZsdrgnHOkfr+1Bp8;Ov_v$g)baV$eXZAsKRTD9>n z=IYQ4w?6`4uWFSUKGl zH68zp;rhl!8{3nk5o>VCuagzsTZc;y%zCHgH#cZr|DEs>+u7p{!F@4uAeb0@XL`@? zCpFHrHS(bq2Yn0byDQgoWp2NnRX`dx&{0!{t(P`J=@)vl&N2FHg{9HK#mDX%8Zh^G zd^_Y`CS`dzW$XgtXP{RZ6nAknb10|Nhb?q|BizYEf+@1W`S#uiU=g>)JnEMN)Tk~I z*A9BoII8+MV-+SiFoe$8+$i(}zEdv(}YL!gVlM7;^ z=G3LmI>#D>lNu0X;-zB<5!lt3Ya@9FsTWF z&o}u+0HZ1TSnHeUt|32xqEQYM^W$z=`XSDy|iS#rAt#kF#G4rOoUtgj(bo* zx0qj1O&l)|x8;cU1!(JLS|H3OdYurkZ1HpAa6!yYn9D>B#|&?|(9|kLl3yh70Q)1P zZuLT8=6%{ii%_9mjnk%V9IW`3uldie%-`Skyj7AO4u zGEP-Ga?tfQRVcVu$nnVTz%gTBo=kmdFWsXPq}9^@O0*I;e{1Lx(vA5ru`#M?1AwtjL zna=3aEwlAUM(8uaSAWzM_o$3f5!KkQl;e85oVkzp1z4A)fXyk8Ox%$4-&`mp2R@XR zJsJ}-Bcl^wZVJrRMGpdXOy?-(=FEV!4)h88@voTYa)Z6%VZr#@F~V8Ur|KIGfN^_y zogBMN?t3OGHIp58-2Gd+rzn>8T#0w_?YjT97Ng70W79?2a{BIIyqka#+IpX0RWmOb zWIOsykuMefcC^rc#1_HQ?mWv0zNqK7J}306ip12G+iviqL!J)-%9?(v?qpQ`Q+O+UIn^4E)@ovb~S}~P%GTBPLPf2zP9l@cUKJf;zaG* z`(x=B7o`GRsdtOF*6+Jf1a2>cBFOH=oI9B*bpVEH>2J;{rAHw_VlZW1ubY#gM1>)* zX^+6Z%)LKa#2FRxeAhzzN`J)4#)PRH!=t0#me$0eVY#vJ(5uG@v`_``JoWfS#*cjw zFQV$oM6i0gxz&}DJ+m`>-S?xa)_$CGPOpA2&~6<0W+I#qjk9#$y*Drl=}G{>J7Ey7 zOHJG?oqZ;G>y`+%YKu$Ks`%RrqA`d^GUFWY^;alG?aH8=jy$a>dcb&u&t|1V4L)a` z9EX>3d2i$N6HnAAT28QC4H!TkQa;E>#z%xRXQ$9`Kmo%@s?vzg?-W!+0Pg%<;0kTi zV(HPOkm(6+$#%yt_)^U~%|33MYnvB$B9ElKK$MU5U|Re#QX%x%UeB00@8DQlLwcA@GsK|PyDcc`zo!5cr!b( z?3oQ9)duXYuaR#P62>jQs8gz=#Sd}a25aP_0-DV_b3Q?pPip<;)9`zFSKfzE^0fFm z#GN8fl3oP~`R>wNi~{3RWveKU05w@8;2LMuX%%Gd4;?>(1KTfCzT#q$u?ot@mO4JE z$^(qv2*4WY&~Ui<0Lm>gS=x(%Kv#8WcFh-2tTNv3=*7oBEA-QO<1_|@K&2R9w%g_h z?EuKxXU<4K9v}Ln1k8sP$fG!hXuJT#$*A3&h{qH;;3bOez8SRn2b7-C-|emKeR>wn zUFTmS?r>~O9>|OEf*Yp*Z7>e7U=(;Hta=e<YRN8ZHy+wrP9xe){( zzZZMqu=&yiL==J-w?pp%gCC3JGa=$5s8x$B2|}l3=w-!d*c)(B^@$xlIss^-S1IGa z^NC<{E1F`b4P|oAC5Zw~hfJ094#V7K3yI(oyYK6!{~N#{W0667h-l7{guv6cwOSR2 z>i@;MyDg^KMnWd<)fgHX#3FpgOKB)%FJ!5u0PM_RTRy>0EEOJvq3odm zdmQA2L3;;=@)Qp?8+iflRm`2IAca|r{EMTLmFju_=aaR(G!)q326|$Fo;(axd15=2 z=wcfM4FKHq85iOM-+#XiuQSed6bs&mNAsaYur3cUxA$zF{?=YAV^;tu?@vlHq+rI| z3xKp_0YK5d6~WBF&#|Xv{;jo)K}KFzm`9u$UUy2AA*?)w>3h%=YhBREdJjPR{6X&M z7OXuZn@2llPcKi6Gya#v>>1C1dD%*hn?)aK1~7Q+fFdS<&QuK8v6R!`h=^^F{WiJs zEc^e?uG*P;W;V3=e{(8T=gv__7$TP6+Z!+}Yjo2wvhauQ!>F$N4!2@~U$Pi@yak1% zf?!h^45jcRVml1E`gILZKAL-l|*^c6#m1(cSLS>-$GGH#k4{JyUJD zwo|}%o=-6!L`()e|7D(H1vHtAA)ZfpKuQ8w zwpr1?#kdKS^M1zw7jVTu9dU6NIAtS3mygo$f`9}i+!J!m>~TiP;OUBG?yT2*l+Uo2 zDDZ$tDZ=!&eAM-Ah&h&l{Uy7}guzFc>wR}4Y}qRsv&9tTD?~PyxC6!%_)U+70iPel zb=j$kG5|kYtkiUkT zp$5o|U&Vy2-Kym2i|8I1kpMlRJ4IoadS8=Fb>@KSpP2l%ajco+i&6iMK>RC#{QrfyTCKmx+bNh5zhorSYrthpof$I zQksOPh#p%Nr%(Bke=Ys<$q#5Sg3kW2mn2E#n{C?hue>8XRN}|%T{!PJv`5<9csFE= zHgK-tWE=+Wxyp!dvk|Wy9?Y%3K5FsY|I`WL=M%Vs?zxvhF$sgbb_fa`sE&H!ByqWs z_WDQt4+YjVj2+@cV1UzKj_)B6`cPsX>*e=q#nxF(Q z>b|c%^z?gY_}kHs-u9;}ra~fc9cV-6vm8xnp~y~O&>!XwS@M&r_CU1(bT@ z4!tbIZK zIK@zLRDB~U8g<;a3Zqa8J}U+nml*|{QhE+%kMKn9%G-yE&42j%Y`|IeHEUmr4D9?OazqEi}rc= zUx=7{pR`h~jI#+V>&sM?a+L+QM#64ikdy4;u z;6}I8<((5J+aD7!=?*~iyCTpW?6{lZ&nr$p+l&DPK`0~|VhKVEp;_@&5gt2*OZrWw zk}wXWU2=x7%Wgk{2#?NNBk<#yUvI&z(>cL&yX-d=AS@ z|8N~nq;rsm0@!B617s^JJhdCG{516KbklskBn(jx)8G zy;jiUgOsR)nV8II7a$zCVFkUD{@JC1$O?1pp1}A;wWr*qczHszPx*;+)f{rN>{(GH ziynFeO;n*7@c%z}O6#DzF&HxUxya9e;_aTph>19tQlG>f2evGwy1#LM&qyL!h;RW8 z;EwzwWgp>RT{S@vUI(YRT2XS*MsaR;w&%iaMUk9+I$2*+gF1L@nz>QQ1 zkKDF|t?VaQhW0S-8n-446$fzt86Z6o_ZvSyg$Ipr>2JzIP8KiKE!DqS1=)%36SgF7 z{EKG2vCh<0L6_^(FauANhI_`O!Hp(qM2!BeV0I%v$)xVTz}mA>;D%o(!}I*XI8BT*mT|Nl0rXHfXH64?${*RXSQPA`NT zS)^{HIAaJmTDNgX0_gnE0qlVnUFP%O7vdUAqE|WSmpNjPYZB|>=c7cjihRh3h?q@|v zYv=6dmIZU7m@-m5Byskq;o#U2PubuXbd!`dw3KfWvIPU*mF;H8&$6w0)| ziSHkGb+iZCiXA%Wvd5OSAMV6&8k|dL`d9hQCUTcDNdpNo1{%NXJ8#lOUZXD6T$AET zPVju+D~EH9?g16da{^Ech7>Tw(HxF$?RLd-4No|LX%Q}4D#ffs+N-~Kkz!M(+`IAX z)1`l+uKV!AFJhQRGgg`W>V8~*5->^Rw(&sg_jh~@T)cRNIM?uCeYpZuDMQ+EB=QP| z-n0$)&wJ{fAPLy3%Brquu}oQUe3Y8y%9Nd-?t%yCISN}V9vz7x?btZ``-MyUs4E|r zA>)5JY3)q5;bIKTv^{k)f8lgVYc_QH4oeQBQ5fhg9y@i~}e~_{` zEzccUg$fKy^kXcS(_On?>f3KK{jtx1M;&ZKK&gCi81KZY@VZScQ9Gd^?bwr)!SPFv zLwRC>Q^R~zXnr}CamLKl95%)D_~%KVgGG%)lR|wSrpm_3E_T^SbPY@6!|x2W zT)xphaAID7r~wh#bt-{LpB`9q=YEFDrl=W=N75}~e}%f*mGhOd&*)CvjH$-H^AM)} zXlwg@_xf1!%exn(Tz3-xuGrJJUsucO+~M{3N*+(V)}|M+7{=l^Irt3oqYi4^9t!0Z zp@-H=|I~pZ?tLwpb@Ep7I<;?g}>sYo>^k)$%2d(#$NOlYhe9%?aCB{nJw+E5<IEiu_+^&^EyPR^wO?#V$Gb6ih#T?uLAl?D&l&t*!N-2^i>4(v5KDb z&BX10<$QhUPMe=^lq4?>Y0?G$p8A0??{|O}MW$E9jQ(hU zlAL4WBxjB`la0Jq;beJUQe4IWYAI#kyGBfPZ2hVoD4;-$ioN;OxN+Igu#~h*MujDb zrZ~!NRghFw+_;)Av~+FXyXKxcA9kNOu!;nk=fI`lzo<{Xb4+=TT;vt-$qqP}LAme$ z+R`H@(uu*sn!c=E;>fA5`M}Q>yg(A@Ta7*0WUp5+k#7tAQXnTjp=oRVuh_vu=tNa; zod&M7D3LJqeC(Z$$(zNJ{2_C75!tc~rV4E)w<#S6$ZpnSL zuTgZv8tH>NIIpnTWFpuSk;b$QobPr7VzEP7j;4#BceC#$|Jn)88yU4=1$Qp4k3`+! zyjXuB;mi2W4R9$!Fm3VMJt3vZG!hXejD!JgZe^21%iM4vaBk30~U&+ z+cW+Hqv4}1M7d5E(Q&||M1GNE^&Lg5EWjs@&<@-=$*U!o5WLeSQV{8nVNWIu$b=?d zDwM=Aw&cc3fH!i4!fW#G!QX9mH?&mAZ99iU`mbo(3n`)MBF+&G6v)>$wUg|@sFZ7> zv%So3QrkDGsyto2-1PLj)HaU-VEMU_DHm;gvAj|Bc`Cir*;B0dzCm>Tl1w#Be3I5U z!opGuc2CpP!aekSJQZ|3zUfC7_M{wYQExE$)DXN(ar|z4nw^rttl}u;DD^2G{OGDr SisHWF39QTyo4qjci2Way=cf|@ literal 0 HcmV?d00001 diff --git a/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@3x.png b/iMEGA/Images.xcassets/WarningStorageAlmostFull/storage_full.imageset/storage_full@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4570be8b955cffa5135820fab3366e1e1c242a GIT binary patch literal 16673 zcmX9_2RxMjAOAkj-EsEW6z;5y$f{_$v!aF4P>M6!Ss^m+9-NF6O)42J6+)#{a%V*) zBZ(q+s7S`iI^6g_e*b&Dyu4oD=lj{~{d}M2Np*L1mXTa02>_7U;^Mdq00>$7!;7IS z_Zlmh08oavINI%wf=0U`$Eu^CadL=mJ+$gzHv}19CV^|0uGGKiVZ6A8JYQBKLC9Y# z>EsGr5K0z=ZTzk0e;z2*CXMvPg_7O|b{(2nteOyb+ul)az4cEpliPlOrAybZ zNS}tbgdk~OjWAC~m0R64(8Ww==w)S*Va?eB#+x)RrOZZo*|NkP0p%&{(*5y2j@e0X z)-ya6L%L-1I#rL z3=^6?d6&7b-ZF4$ue3k3jxSSqgiQ}TADv#OeA<&`e!$mpHBivoa?c_|k0V1ZIktv zLnZK!pBnt8zc8aT+kH~0k9P6sDP5Vtr3(u?(>d6S2L0lr(!ybDZ>hhe-C;tB&4i@+ zs5_85Z!p`jW{S()1gq5lSZ_yUh+={uvW~0=ed(fNcFncm_DCYMTsN4!C|>a0s+4P` zV15V6{9eaq>{lwmGh+--WW4QOC)SxaCwHVBtGf(5vgKQB_u>-iGMBh3X6YAbf0uuB zuDJz8Enc85vruaXP4r}Pf`wA7jA&6u{&A((dWF6WyRjjDss-wE45_q{P8hOHV$M>! zm7*7uqNNYE*ff&Tj{)p8ZlqsV7MH#o+ov2ed6-Z|OgI!?>!K@%)aKc%WPD9#zJ3^` z$hYwFT#qN(U?bFSIv4BSS6TfXbx)q}h|g?`HudS4`##*BO}G3cX}tIp z1lX z_<#%;-1ix{E)bfQq-fyicwQi9>*V^-p73{`aOME>MYn+vP9axKOX=Ng^UENn@hmvx zs#(=?GX04q#yotU?G<~KJjbhNUgd5ZINz5<#;dbL=P)LCf$5;PLXC8`+ka-NR_jLi1AYO<+00CRFJEp+YRFOsciC?nehtBw7OW` zUe+mWpgq?vbST`70q*gwQ<>^NCltx1f&|l}5SGJ|OeHY(1mcP=u-Fa1!`;QG*O<*S z|FqNCW&+dYz65?I#y0~ZCcGfirqcM(vPiH?C^yFY+7f!6sD$d!K4p_Y(8uyO*mu25 zw>A0k6Hoa-*IAWfLSs(9-0jhnmN^n|D?@E3;!o|ye^27T7F(Pvq$#dC2EGw#Q}41Ij41C^)wGdOMW{u@j1 zr8HciJMbk~nQSK5T$nQ^20Qv5(>BDkEd+A%2LAjhlcwa!ow==&HTZAtJUL-H{*A5K zRN1AJWf*hpxj@e8(ATqx{_3c^b~J1l%!!Sfoce4)JoEClcJ=5cU-JeQAwdV6yVCZi zen&^(c`WG{-C*+YL1CQ|({7+{*8;;1yr!P<}P4Kw1IwnmE!p!rOtXQ&cf57H>O=|S$6&=P5i@P4aQ{WaRW z^O8zew77RN)bWwhI!P4KhQT+iZS@sa;IENrd5ZP5jJLuot>z6Ad0YX+Id4Gx(T;lYu8vURv&p{*#hpLF8n%0&K{2ia^%wb7Mu z2q7S8CkIswnqAB3!P4}{uRv-q_!IiH#E*I8dG3&wH@|8+Cbe2|^Q_Q?=SN{6WT{C5 z?eQYr@!OEv_$N`J*!*%=+sOvDBX24?AJ665skz{9-ew$>=RKjSrp=&qGDU>^nRR!}d9PIhBlCR@%V zE0YcR=Pr|<7n-^#AtbCM#FSxt$J%NKU4~NT8iFQDK|MgY=R}tI++tm_ZEg0(h_<5`==6y;5Hd`tTWy(7;_4nlTlmx9+()2wtQgg30>z) zw=W>iwJ&N8V^b2yVn5`$$t2gMO&!vd+kB$VD$kYkS3E>j%g^)v-gn#9rx+i^C6#GY zTJ8C3IT^^`ojiE5O`0_jDVk8~5D|Vd^RAK2XHb9K56%?CYbjYgY$RfIu>7?wM~bu| ze3U}av^lDh>-0!ar zwENjUe6!gf^lcJmbm32bRWd2Ahy%H9&%#TpU{LgMg`gTQ&~J)Cj)h1rdHKVctkaZ>D&n*{)Jo~?`TBiOjwPwH~$a=gOIkL$%aZ@Z@_=+kSO^48Cf8i9JZ?Tdkj=?Q3w<2Lz+ zoWdZ4-NBe-$y#R+rDTg?;;JG`U{XKX?3Y{Hr0yX~RT+hg|ra_)jzbzoA0fZa!mr%l^C^0lm$`?9J>n0N5w;i-i8huV6M1v~p0=W1+cO_Up#xh* zf0Aj_xSz~~E3FgT@Tf-CDSgfjFQ9sWw`*8S-5_yIo<&Sb^&q^(9W>G!3V-S$?2`8} zEfn!0MZAC3Zi2?G=6^7?qPq8IS1#b<)9sw_{2qi13+E7foBCp) z&wHFKoAA?TKn{8)lP_-w6HMS)k2Ye1={qGvbj%Zlzc&YySQ=f$u`g`_cyMFQIt0y`dD$}0kby`*2zW?;g zH|_{i#3#cR*%MYU1ll+tzX zue(jCQuC+3c3!KOUk$CZF;9_(?*x%-2_!X1XK_R>J~FpDM+}S(?GvS@(uSM!sz+20 z^OcuZuMaXDt_Z8?txzuhsLFa&is=g7h@lLsHczB!CJBbMm0EJJm>Tn=>wj*Z zmXd=Pf)FiuORhMN_LBS!G#wG%^ZG<|A`0}_{P{if@WG4&V{#Kx--{nngMNUo*Uq!n zf5)WTY7%(z<@>12C}{plqn)T`m-~wJVQ+%;=+^h=K%7#&#o{pn@5)bzMV_5cp>nrn zE+$v@Z#yc)zVqput{8bw9q*Q&{o%#>hYQ)@6W4Z9>E_vq1mNr%X7$r84;$C1;J#T# z2@9XVGb$~%ZsH4+ z(ZkxD_EGlN4eMI#UU)$Y*!>J5{UPM1B)onm#XIs8WPakO7p_RCG21ilMipZB(6NL2 z8l@rsv8`oKd~jV)dFRZu30qwqbhE{06V}I7$-(7601jLG2{U{*{~@M-g&X;b9+1|4 zD3mxYx_Xn&d&~?#dJ1i-8fr>Te!I53nnBU~77;-W>cM^(u8AewBf^vE3AH}5%P&iV z04p-F|Ff#ow1fGyOt5fZN;v$k)KyJlJ~0OQwVpLL>X@{aCK1o|yC{`(e#q{Ym%bKU zUF(2E8=7pC{I_Eh7Q5PwnTZAI1G`1ob~)d1x06Env5-30HMHk#>8z&s4z{W~Ru9Wh zc?hLnI7zZD_6aAnIUqDGRz`y38R+hS3}7y}g-q*y%JZ2COQ*M=DkJs{W!r8-iDJuh z4x}fa)?Juef!7o5&SdHg3=K*n^6wuC|AdLK+4QnWvmD&)U2%=O87tBK?%SBsY z0S$66b_yh{LI%U)53yXL0k?>rl)q4Tfp+H>IgQ1y+b8(`Je2yQ@`J_PJqpDhH0;(y zwbQnKE(c+&SBb&XI|(c&ncKzbD18~;+HG8+^+F@mwFEDS5m5$;&tz*Bo^kSDT1e0JL$Y(XUaIyZ+KgZLO6HXZRx-&clMhp$rK(+B0{B#naG6!?V^_dG9S^{nu zeeyTaW>jY9Op&5jSL6RNhMOIuhFfm>?%d#CaxFuP|0!F z*(rmG8IJ2@-u|vtcKxGUnuvYk@2*GWn|a|6bOyLP%dNV}e0BU&#&3ZFYAkNKkGT|C z5g)Vhd2s4wbzCaU5=Adq>VxZyG<1aUrH8l8$u$U z$A8S3JDphnddn)GB?ZFBGIH@;hyD;}hYmI_JbFP{5=f`n@?}sD6H%uTEnYD#kC<;> zr`D7IZAxF8q)m~5bmd0AxY^rN_!|*LTq+TtJOSSUU`+}PGA{cF6rITerUW`s%7P9; zT`z1N5XopJy5Ma-ndYZ!5TqfY@$m&iL!(28S>I9OoC!Jw*gI%5X8bR5Y`!>cdHR}< zHOHggTg>wuC}=+Ahx|#916O%h?E2ks`cI`eMdw?E-fl4Ue6i5Mk?c8%de+rnv$9!8 zGAKvGnMr8N>f&ls6ma*MQk00vZ>F0tXdw4(db=fj#?73!XT?SAV*epFFU|>2U~P^K zYqyhqJ{I2ISrg%7APc*?#`mVs?gP9W^gZ4@PL-eqf;S3IxPg57LtgTWrCeGl&nQ2> zrmSjwIW3q$DFE}jixhRMCaZJ7iEAf3Vz(i9xYQp5{S_W+JK|<4yvg}f%1^z^dD`M= z$Lg)n{Nyw3o=qibbgV*3g_|JcZ+RNMexIoSZeBb)zBZ`?dY4DTcemyumO;nbe{Q{R zmMb-T{^(m-c(%hJ-!$jjuhP_!i1V)g9)2X&Zq2-#>D!bXzu*XH>WuCvN5@jN&I#VF zrz1pSH^}dyP*ZuxqVs!E8Jr$hQYwl^mu5p`M;<-aVD$dP8VMBYV z*A;DLb%x>e&XwHM_lAVbLzHlkU#+76o*ka?iJzBS2uhvbhR@IWs$%{%db7pPH-~N# zg<<{zd%eA6m0(|7WyvpArS zg-QM5T1U^A3DF}m$n@a4R*N%e{dTyEFQnakdgNHpp^UnLdI#i}lKI*vn=2<@6AxA= zBZl+e8Fom7eNdE$hXKlOrSdh!rXb`*&6;){z>TSy%qSMlbbgFg%p%}(WQHrAy$VgS zyhF^6c>!6a*W2auh8He=Qu`A^r#0x-$jsbtb1}hkFP)9uyX(g4?}@y*m}T%&Dek7Z zIY&Un+3~MD93hIznCu_^+widr^Hm+cU565>&8&ghT}PDto>R z{+P)27>I!fh*y2n#Acrzjq+_ytUJ@T?@Pk^K#emJ{o0v~Rfw(Ff%dm(fM}nud@}{4 z9-dM4LZh_xo&LFz82D?+$^5-LekM|T%?)$bOFkK_mxEtm=KYf?>Kn3y`OgXy;zZu* ztIof@duef5&g*EyoJ}(C&UNPRRc0^CpMa08O-f6N-|j~K^`|j`A(AgX_1o>w05M^N z+yL1#g&I)8mMzVdd%obpwPx_NS+`e4oL(6PFg zb7K11?#6|Rqs|#5CSC5B)R6&Qr)h$VpDJf@BJYd=xPC>NUZ*9{dngQZsFr);?__mW z0`f`k$5_|AixxEEvmK4KBq})?|Gif|c96J<@XvV`^DGcLsf$i)-c;Q~7oRyy zlg!_3?KnLnu?4|6V8IDIXU7>L46d2Qe7vBk(><+^4t9^4-f|;O)Gs(>#OKU%?5Jw1 zUFmHPVdY5Q7^^up_0Kju{@S2@KYzb_p&Q4Jgaz1eDBPkg0cAMl*+~9b=R@y&cFS51 z%dlsu{yiq{KX?>UExsP#@mXUT?COuPeeLtR_WRtVvNdBDGPw<(a(#wldkjT9&Z6Kp zxx_(5%%%)6w7qQ$v=lz#6{yCxln*td=KgxVI)3uoXGB@5DE`-aV{74ywdP+-Ox`?& zMQ=OWTP)CU8LyL($v3@W=1*d0FqLqiuD+2RP{$6U|5-_=szLzugzFEfwyYL8&!;cc zv!8@r$a`e`B%k%6y7}|#2DF2`z6G`BD4p2U{2=@u! zm9CH5IA{1UY70Jp)IiS8%l%X9UuPMFFFX|G-(^j!!-mbRZT>fD1)VGs)RZwO{KJQs zVjm3ZF4GN28-Z&Q21N{0xqT}vg(!*)n@EzY@&M>WaQyniEC18YTJf6hnoKfHZQpjz zA5G!=J7=wIHy)h&kj0?fsXrz5?)nT0e3rqf>G6Hh3-jiH7v{#bvlRAclV$0X+oNa` zXF_r}tr$iPjjC<+>ROe!K2D>N^Q!imQdA$a9u4nhlfQk3nl3Esj`1V0vqYBC;CFi? zd3PO~GvZT{D?MKqQ*%5}XKk!RJjG(~LCpf!W$LH+9*qeRzjU5N<$LR!0-%7-6}wye zoXn+HT!{aj46!ikqF62PsvUFwXxr@t)qvlwa?tf&SL(Y;D%kA?|DD}Y-M3qqq=j{qu|~l04o;_YYvlj_zJ^^^#Xvef z1!1Smu2fVE$gm}eC0PU)DJ*xW)U0$%jC6RZ^)@TO zJZxck!N6^b^84e(rKiL*A8*Cq!@|YXM9ZMcO0^WB%`l3Y_Sgd6kjO(*#L*`$*4$>O zzhY1jdfpiTSiXT+~mwbGm<&bH$8BvaD=7qE^XEJ*>j2xcwJ+YAHdo<`oJ+Lq1Q`@r1T{ zsxi(YNZaC428spT`#(}&t}9HuV+RiZc)hdc?o1;qLPH{+@!@z-%_<5%?bywAtn#gZ zN*EH=4iW=}g`5LN6w(javqJMjen;VgF+O5h^KArN{D3Yu%M8oJ#rB)f_S%!;KdUq= zs9`R(K+?8=FAzOy5nbDIH~!Do-cS4Ok#)K4xtNHNY;bpz#|i#`HsTkQl=0QaiQt0Q zb3t#AIyQ>)cC7>aZI7o!G&XkV*R$;9PZ>zWl<~toiXR+J9STDJ8CJgdWKAn4a1VdW zB%d)P-Ml&5-@dRf9%K^oRjvN1VeUuBa?O4L_uJKccByc({@m(Co;`~eqWb(*$46-| zaTQf=xqYtx>}mp~JhnROnWy@mAe&86|3w|Zt{x%a{y`YK>PLTk_6|!stF!G=v}7&@$+3%cGqR z41uj(OVjtf6>tp~vd15FUeUaz+@b)xp0rXsnI%qs-S>5uesU= ziioiQ6@Eev9$X2x>w~lMp$$@Rh^`@{Ozp??Nd!?NaiK2wSkr1|KBYxC8}e zr^(X*PX@nlkRSUFE;~MOkHFb#kLXH3%hufv&Rcq-7mR;hjG#|^y|qVFX&yGPXJE%y#tnXb(ToA7 zHfObpmCXCNM_#vrTR;A>TI1fCw6E9q+~QxFAkND8t%m0{$fGK#p+gIsn z)qngpNL-d~Anyj5FQCf%m=F?3_l28G5(i2#ln*}QoQavu$_LQr?FgO_Z$ozc(*+^h z0~LlT;+*Az&D8gt6$ttivGW75{R2}L)SuQ+u|{=t%-_08!*`@m40-Pg$KAznzF^u! zSogqr9~kIKV^rpv(M^R`&ZvZ~Of5vh6<#+?yDF-%j65d`KCq)^eeYQSy2)N+*E=_^ zuUTS9YWdmScw;4q{BRHhx;`4pAPds~*)P_>Rsl`Kj}=@0bL2Az@JzV8$L_Z@IIRhK zD_4V(aqwja*fLKJj{x4iI0|zGq2WJU){ziXpY-S;lc75x+(P2Z{3=9@B?Gu3sq5s= zzhu$pv3-Dm-N?=h(N=_XDA8Tz#Brr)Ga%7R)*5pd z1k}L0&iRIH;g4sueI=%a5g@ZK61s`u8iS}32$W0!m-#se3GhF_9}GpcPbWZ1bi&a+ z?@LYJq+9I)neTd7fK%rS-1iBO0%U-V0Zn;D6;BU3|5DFLLOr9l9^S<_oTxai`vXGK z!_-H0K}d)bIHP^+KOfR>Y!ku2&sOyCo-z=6?tMfmJCxOhUm_&I-}=i>*Hs$NUsIy5 z1hKJa!5Net@Bv7VCS0~Cv2~s--~gzNWa#gyqS}Pg46DcAiS_cmx$>jbmk(uMJ~)mZ z2S~U$D-dw^ZOi~+kMo%_h0z5@L=GV z@6WIPjWOObuuVnhzl=0II(uc*{jBeu7Z(dvyr#UbpW6byi*u|2^*IiykA8YYe!H1y z6HJ{2NG{X*-1h@#B6ce&V$oopxvbo1y#4eqto&A0{n9L3sGAJL0R9b@@_{J@npH2+ zKqkMUPX`pdK1Y2O6cM1**lrT!fMzYYvfhR{ch~Wox4Y@lf-jRB3XYbq!BbuWM?jj8 z1Rh)I-r$QExfiJ^(>xrL`){j8tzj|Z(s9rJPLl^JIUGbg=i97*l(y-;T+wN{E5*i9 z)+r%JG8CM&?{@#~Py$!LYCvKWL9|f7NvC&jIL~#td{W9Q@W_z|ouhpwpPJF%=%>N1 zx@M~k->Bv`(xctY&7A==**Ct8$A;FDkfU@lbY^v^!w%l3~ssLdPMo$b2%KnKFOAaJ%3!yUwGZ(mXTf99lW0;+8jgJ#4Jl@QqAQH=++88ByCf2kFg zDo3A*STadvxg_V`VFD|Hz&d;mjP{dD04o4CkHX<7Ddb8iJRH?{5~}UX1B3-3He?-^ zYo-Idc&%=Lf?ZNY2aBW1iH=E9&9K}p^2jEBoFwS}?+V`mVw4K+8X#_VAXpLdtfQv` zRyq!NYd5_HxU!7|N#NNhUKS2hIBo{WOFPi22zA%d?IDBDU2f_Y*xJAjvXC&Bcov6j*-V{wI#u4uf0>E2v$fUnePk0 zdLQAy87RF(m{h3=_negE-1d+Hl>`u?4Q}AjtYDmjdHfwck>DGStAg1!LyUKzPOQdp z!y92xz7$71uI=?WT_DApP)7=H!BOckP?R_R0l>Cyq1N#2S6dc_^4rHe zfIphBtRZ*7%jmc{7z8W)W@3ip3KdvaRD%pM@%hoSeWZPl-T&J*EPc-IiD#`&nK;xO zA>QWUs*fxhB@9nsC7GJfBZT{F;wxtIH>}@|Ry}lVtqi2G%iqGsSiEAdINcgYSj!hk zl45y@i??8NeeMsZgZS}-QLbgLFG;3#u18Bj0+&xeQ+`0^^j7dt=mcDg=ni0NTtpY& zPj&^1DxunHFhvUg44j%o4}7(Iaf}JGu94R>9piEY;WK&Gtrt_LF{LnX!4&3SfkiNvSVik*ZEflJC&7J0{Q|Jk(%X7&;E); zc=cdKR#y)CHrHS908k!ye!mQ&>8RB6rhq@X^{sq>q|=UqUDq_p>{$~e%gqE}s~2(9 zH$WIv{uPpj)J_GQJ^+)vOgsR?Bp-0gXQ}x`!tK@hbX?*S*_BbT3}w z{#lWzWd*1r3HML>h?hx$aBT{!?%2F?}lwh!CCR1a(-luPf_wwP3 zvQE1J+TFUf()?t4)`vD(;T%Eb*R%KD<&VOrNg#QQNPQ&4?GuR3*Wx*c+xLruXR5&5 z%YJch*Xje<`<7qDb##EeA#!P1SjAp1T%5Mck*9c(Da1kc`CJJ6hWqIZ#(T(vb+kx< zC@V>xB~UW(dx|OoupzUKu=!EU$@kAykS$wh6mIjNL zLIYOk)R(3n@Golr^Jg(y;Zb;|%hAF8)^aJ*LSX)J9Cg>pNcYejJt%~_serh>DlGro^8C{U z0AFstG1}zVqsd)Ct7o)LvDK+YNWflYuTLu9H(JFRZL|sRJluXtVVrbLE~*E6s7#)8?1j;@YG zAD{LrJ-hrE7RO;O$eAH~0_KBoTwf1h|NJ}Uj70Np@LR|Hh!{Tdd8(`)Vc|>wfn%Ij zqIjOCuC}pruNTqpc=+P7=hqJi3RLlzhGa=?JD=xAHx{c{QFE@;Se-|lD^?X~U7QIx zP(c$a@NV~Or_>+B<}%U2Fu@ckx&q>j%^>5)DsW}^Wqg$+3rlpMe)QWbKXxY+_Kd}U zbGdUHbDZ;9*#$S)TrIPD0p_xW>KQXakvmL~kTy|BIkFrdrHsTaKPD<0kN1l)`o7s_ zAM$~imtW5s{@fm6r>ZE3ZLhe$SZLY#Wp5qzwRM$2xpp$Ok&YuPz$Hq1JhoSoWhs>pYL~!Ukx=&~&&z&u>j5y4 z+J#!SWH)8HNHn)VU~N)n%1ba*5KgEHfO4E5Mm*sO!8=zadwtUj39PPvNe2ef7L!Kw?+Gr{A6Iek=vo_H)_#3~QPO#LW! zNZB#Nwp+2N!dg6p2WS&P$3kT?oN{bNBs`1fjHmg$qpDCK27@pTzh1qnQ)H$*$6bXE z1ASL4Ndb(bM*b(@&VGSUf=O-`&?@9Tb792;jjb#t*4d*dlp`_V5atA1@U@k^Qp?&J z^5s*Fb|4l*r%*zGQlYy2?il`j_ zCpc;ratMsk-`s?=)$CDR_Ij3n_ke&dz5lN_2)%`4WKtF%EH^|_QE#fz=Fe-_vb|kDbIe|!bjj3u93eE<=IJ_*j|GHrX?V`~*VL9s1z5sdT<6eT= z%J_E|x;%g)npthtLecNO1q&>9MZk$(Y?*WW>@$){m0j3n?Bgz8o04 z(b>JCz=qsz2{fCe9e|b|1==o-`mg$BbpN`rK!F+@CH8dnb+qNg`aa%|yj2dkM)^e- z-Ga06s8I#WL^J1GBH9*iyj~E&maE6%sGNbwsZ+RXXBDFCQ7cZAxVBYKC!BprqZLLS z97SQ1P0w6HnGsSue-yCW;xc-4YrkrHwGPL|X4Usx_xAf_T zUFP*R$>8@(?xJhJ?>iFIj63V)B)+sP6jIle+d+iWTMa>M@xdbTh7&UMbKtkKa7{yQ z(o)d3q$yT=*^Py@;$@5V6HedV0_9+}l_<}8Iy&jW;c9f0Y5T&uf+&Tjr?r=GU?lq) zz>_>oaN=Exc9G%PFiG#ldDb|tZMoLxexJu{?rG}5QHgUSB68-X^Q(|WV;aW;CQzPP zQYg~q;(9G+_-TPA{P2mk~JzOCc(yN3_a7FP0)sw1-C?)~f1MrIhYfJUrA6s8P zxP*KJ?Y_!sslubDQX=#@FbXw|YB|BhXhrMm}5 zG;^$RoRxg~{U{U5&_^f%udUe2j->=#eoJ~O7+N1tVkuAz0blmi6#9Wh*LTg>*s7%= zXAg*WDyxdXtf3`3KevKziRZa3$0U$b<>?t7KuWRzeHFSuR*;Uo$QXqs%|K*--pF9d)?yUXK6;Nb0XSNudNDfzOqzyNg_53cvy3N`pd9C zw_?jJbfM4c9v~`CH)v{gB(zR}m9z?l{d;rHow^HFWU+b7 zwIplk4F>Ut05&Ev+|aP|R`gz~BiPbkkn@;m3Jr88FF_63Y0fUcQg)^(9dA z`QDnn+*@#W(@*o?Wy2>|!L0{@Y^{hLnHR6bx~)8OT%U6QtT-mf_uF#s8D6MaQrp4#Fu2Jq9k@h?zMH^SI+)=t5j;^s7kA`@%+}iNwYHcTK-K>-)j$`#~-yn&WU!U zDN9nQportR!DegEi0wp=-Wag?8Ge=84M&KV#acv> z;cq;SYa1YsGbc;r7@-rp3cc4O_0xaztukTHo%}o(j;QC2c=v(N;NpPKs~3Uj=Yu_K z6DyYf|NZu2EYj!M(28fVCt}vvY@z)*rQW=l*Bn0n6}adh26rauDtp98TJjGY7eyBl zC^PL7a2yobj!gCVgMpc4@vV39YT(gBY07G$Pd#@(9Yb)UuHvgXv4D${FG?8Lqe?5NS@(eS5_!PKz z!~&#~UsJbWF_~3(lVoqP{4IKzkk+yJ$%zZU2-1|(x@8-oVP9~Q@=r2fb+LS70_0NZ z6I>a+|6TPh=ot1rm>%bG-mWCwI~1Nt;|o6DSLeG*NFxEjJr_)my&E!1&cwCbtqr+s z`eFE`CGfZ+O<~K%ep0~im)8ssH~1ba&6g9SES!fpJnQNs5W^%@_G!|UK-qU9T?|Pb z^wHkWWh-i*ypuTE&nP-NoM#Po>E4?ThQs&=Xi|iyhU=S(ji10lE#5#6!&;nZ2?hyJ zlew74`b zn^$JCwA5G?EP+*i;KFvvVeOF?{!#Pd0~ayiANs&-G;PY1R8o%~?SOj&IKcKhj6I7Z z%oW9-cqcLuqiwfaqF2kQYb=ugsZ6^w?)uEy##bZXN;4)Px2$-az|;H1gsI&r1#bAs-+hfrQ<*%DjSsfYOFP zSZdTeQTw*K&=n`-pVqGgrYYhU-?W?5esS?Cj!l-B{LabvyP5I}ph(v-05A@;4Xy*Vm(y&QJ!%ki#eHtS>ZmuaMtHAs7!h&Iok z&hZX|=em70O3qvK*WxCq&RBKO(D3CuX`@f&+@y3j>w~20`@)@ZYpC z`NpS`7A^Fw?iOlS61@sO(8ba3(HkyC0fC2g^hkzyneGYsd&l2;Jbt&g5;vyVT0eq# zl_@`#zqwV6azK7)Krubi)Bof~+nQIN@^+TM_`$bws~|h6)izbi9f(RLZiD|Bv}zw7 zRH?77RAlN7nP+X6We&QICgbGwF!`0);u{7}S9!H{I4w(v3|)7|bzo}V5`31lA#@ES z9=APwPsjsZ{gh$^&AR*T_T4Il{kE~^Y}M5kY#sc)W#mUw2i9I#pe?{=S A4FCWD literal 0 HcmV?d00001 From 0e19cbe321b1200f4498b1751cba30afcd9dcf1d Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Mon, 17 Dec 2018 13:23:02 +0100 Subject: [PATCH 13/58] Merge branch 'develop' into onboarding # Conflicts: # MEGA.xcodeproj/project.pbxproj # iMEGA/AppDelegate.m # iMEGA/Camera uploads/Photos.storyboard # iMEGA/Categories/MEGANode+MNZCategory.m # iMEGA/Chat/MessagesViewController.m # iMEGA/Links/FolderLinkViewController.m # iMEGA/Login/LoginViewController.m # iMEGA/Login/Main.storyboard # iMEGA/My Account/Upgrade/ProductDetailViewController.m # iMEGA/Vendor/JSQMessagesViewController --- MEGA.xcodeproj/project.pbxproj | 211 +++- iMEGA/API/Chat/MEGAChatNotificationDelegate.h | 7 + iMEGA/API/Chat/MEGAChatNotificationDelegate.m | 41 + .../MEGACreateAccountRequestDelegate.m | 4 +- .../MEGACreateFolderRequestDelegate.m | 2 +- .../API/Requests/MEGAGenericRequestDelegate.h | 10 + .../API/Requests/MEGAGenericRequestDelegate.m | 43 + .../MEGAGetPublicNodeRequestDelegate.h | 2 + .../MEGAGetPublicNodeRequestDelegate.m | 5 + .../MEGAInviteContactRequestDelegate.m | 2 +- iMEGA/API/Requests/MEGALoginRequestDelegate.m | 5 +- iMEGA/API/Requests/MEGAMoveRequestDelegate.m | 8 +- .../MEGAQueryRecoveryLinkRequestDelegate.h | 12 + .../MEGAQueryRecoveryLinkRequestDelegate.m | 157 +++ .../MEGAQuerySignupLinkRequestDelegate.h | 12 + .../MEGAQuerySignupLinkRequestDelegate.m | 133 +++ .../API/Requests/MEGARemoveRequestDelegate.m | 4 +- .../MEGASendSignupLinkRequestDelegate.m | 10 +- .../MEGAShowPasswordReminderRequestDelegate.m | 2 +- iMEGA/AppDelegate.m | 881 +++-------------- iMEGA/Camera uploads/CameraUploads.m | 4 +- iMEGA/Camera uploads/MEGAAssetOperation.m | 2 +- iMEGA/Camera uploads/Photos.storyboard | 2 +- iMEGA/Camera uploads/PhotosViewController.m | 2 +- iMEGA/Categories/MEGANode+MNZCategory.h | 2 + iMEGA/Categories/MEGANode+MNZCategory.m | 176 +++- iMEGA/Categories/MEGATransfer+MNZCategory.m | 7 +- .../Categories/MEGATransferList+MNZCategory.h | 6 + .../Categories/MEGATransferList+MNZCategory.m | 17 + .../MEGAUserAlertList+MNZCategory.h | 11 + .../MEGAUserAlertList+MNZCategory.m | 34 + .../NSAttributedString+MNZCategory.m | 3 +- iMEGA/Categories/NSFileManager+MNZCategory.h | 10 + iMEGA/Categories/NSFileManager+MNZCategory.m | 72 ++ iMEGA/Categories/NSString+MNZCategory.h | 5 + iMEGA/Categories/NSString+MNZCategory.m | 38 +- iMEGA/Categories/NSURL+MNZCategory.h | 6 +- iMEGA/Categories/NSURL+MNZCategory.m | 206 +--- iMEGA/Categories/UIApplication+MNZCategory.h | 4 + iMEGA/Categories/UIApplication+MNZCategory.m | 29 +- iMEGA/Categories/UIColor+MNZCategory.h | 3 + iMEGA/Categories/UIColor+MNZCategory.m | 12 + iMEGA/Categories/UIFont+MNZCategory.h | 20 + iMEGA/Categories/UIFont+MNZCategory.m | 12 + iMEGA/Categories/UITextField+MNZCategory.h | 8 + iMEGA/Categories/UITextField+MNZCategory.m | 37 + iMEGA/Chat/CallViewController.m | 2 +- iMEGA/Chat/Chat.storyboard | 280 +++--- iMEGA/Chat/ChatRoomCell.h | 1 + iMEGA/Chat/ChatRoomCell.m | 11 + iMEGA/Chat/ChatRoomsViewController.m | 80 +- iMEGA/Chat/GroupChatDetailsViewController.m | 35 +- iMEGA/Chat/MEGACallManager.m | 5 + iMEGA/Chat/MEGAProviderDelegate.m | 8 +- iMEGA/Chat/MessagesViewController.m | 622 ++++++++---- iMEGA/Chat/Model/MEGAAttachmentMediaItem.m | 4 +- iMEGA/Chat/Model/MEGACallEndedMediaItem.m | 2 +- .../Chat/Model/MEGAChatMessage+MNZCategory.m | 45 +- iMEGA/Chat/Model/MEGADialogMediaItem.m | 2 +- iMEGA/Chat/Model/MEGARichPreviewMediaItem.m | 2 +- iMEGA/Chat/SendToViewController.m | 4 + .../Cloud drive/Activities/GetLinkActivity.m | 2 +- .../Activities/SaveToCameraRollActivity.h | 2 +- .../Activities/SaveToCameraRollActivity.m | 24 +- iMEGA/Cloud drive/BrowserViewController.m | 40 +- .../Cells/NodeCollectionViewCell.h | 21 + .../Cells/NodeCollectionViewCell.m | 43 + iMEGA/Cloud drive/Cells/NodeTableViewCell.h | 1 - iMEGA/Cloud drive/Cells/NodeTableViewCell.m | 7 +- iMEGA/Cloud drive/Cloud.storyboard | 651 ++++++++----- .../CloudDriveCollectionViewController.h | 18 + .../CloudDriveCollectionViewController.m | 248 +++++ iMEGA/Cloud drive/CloudDriveGridFlowLayout.h | 10 + iMEGA/Cloud drive/CloudDriveGridFlowLayout.m | 27 + .../CloudDriveTableViewController.h | 19 + .../CloudDriveTableViewController.m | 341 +++++++ iMEGA/Cloud drive/CloudDriveViewController.h | 15 + iMEGA/Cloud drive/CloudDriveViewController.m | 740 ++++++-------- .../CopyrightWarningViewController.m | 2 +- iMEGA/Cloud drive/NodeInfoViewController.m | 4 + .../Cloud drive/NodeVersionsViewController.m | 8 +- .../PreviewDocumentViewController.m | 9 +- iMEGA/Contacts/ContactDetailsViewController.m | 62 +- iMEGA/Contacts/ContactLinkQRViewController.m | 1 - .../Contacts/ContactRequestsViewController.m | 2 +- iMEGA/Contacts/Contacts.storyboard | 69 +- iMEGA/Contacts/ContactsViewController.h | 1 + iMEGA/Contacts/ContactsViewController.m | 157 +-- .../MEGACD 3.xcdatamodel/contents | 17 +- iMEGA/CoreData/MEGAStore.h | 24 +- iMEGA/CoreData/MEGAStore.m | 111 ++- .../MOFolderLayout+CoreDataClass.h | 13 + .../MOFolderLayout+CoreDataClass.m | 6 + .../MOFolderLayout+CoreDataProperties.h | 16 + .../MOFolderLayout+CoreDataProperties.m | 13 + .../ManagedObjects/MOMessage+CoreDataClass.h | 13 + .../ManagedObjects/MOMessage+CoreDataClass.m | 6 + .../MOMessage+CoreDataProperties.h | 16 + .../MOMessage+CoreDataProperties.m | 13 + .../MOOfflineFolderLayout+CoreDataClass.h | 13 + .../MOOfflineFolderLayout+CoreDataClass.m | 6 + ...MOOfflineFolderLayout+CoreDataProperties.h | 16 + ...MOOfflineFolderLayout+CoreDataProperties.m | 13 + .../MOUploadTransfer+CoreDataProperties.m | 1 + .../MEGAPicker/DocumentPickerViewController.m | 22 +- iMEGA/Extensions/MEGAPicker/Info.plist | 6 +- .../MEGAPickerFileProvider/FileProvider.m | 14 +- .../MEGAPickerFileProvider/Info.plist | 4 +- iMEGA/Extensions/MEGAShare/Info.plist | 4 +- ...ShareFilesDestinationTableViewController.m | 15 +- .../MEGAShare/ShareViewController.m | 23 +- .../email.imageset/Contents.json | 0 .../email.imageset/email.pdf | Bin .../errorEmail.imageset/Contents.json | 0 .../errorEmail.imageset/errorEmail.pdf | Bin .../Contents.json | 12 + .../notificationsEmptyState.pdf | Bin 0 -> 4956 bytes .../thumbnail_selected.imageset/Contents.json | 12 + .../thumbnail_selected.pdf} | Bin 4990 -> 3962 bytes .../Login/mail.imageset/Contents.json | 12 + .../Login/mail.imageset/email.pdf | Bin 0 -> 3922 bytes .../Login/name.imageset/Contents.json | 5 +- .../Login/name.imageset/profile.pdf | Bin 0 -> 4179 bytes .../saveToPhotos.imageset/Contents.json | 15 + .../saveToPhotos.imageset/saveToPhotos.pdf | Bin 0 -> 5576 bytes .../Images.xcassets/User alerts/Contents.json | 6 + .../Notifications.imageset/Contents.json | 12 + .../Notifications.imageset/Notifications.pdf | Bin 0 -> 4733 bytes iMEGA/Info.plist | 4 +- .../Languages/Base.lproj/Localizable.strings | 170 +++- iMEGA/Languages/ar.lproj/Localizable.strings | 152 ++- iMEGA/Languages/de.lproj/Localizable.strings | 156 ++- iMEGA/Languages/en.lproj/Localizable.strings | 172 +++- iMEGA/Languages/es.lproj/InfoPlist.strings | 2 +- iMEGA/Languages/es.lproj/Localizable.strings | 244 +++-- iMEGA/Languages/fr.lproj/Localizable.strings | 230 +++-- iMEGA/Languages/he.lproj/Localizable.strings | 154 ++- iMEGA/Languages/id.lproj/Localizable.strings | 196 +++- iMEGA/Languages/it.lproj/Localizable.strings | 160 ++- iMEGA/Languages/ja.lproj/Localizable.strings | 160 ++- iMEGA/Languages/ko.lproj/Localizable.strings | 176 +++- iMEGA/Languages/nl.lproj/Localizable.strings | 162 +++- iMEGA/Languages/pl.lproj/Localizable.strings | 160 ++- iMEGA/Languages/pt-br.lproj/InfoPlist.strings | 6 +- .../Languages/pt-br.lproj/Localizable.strings | 218 +++-- iMEGA/Languages/ro.lproj/Localizable.strings | 154 ++- iMEGA/Languages/ru.lproj/Localizable.strings | 156 ++- iMEGA/Languages/th.lproj/Localizable.strings | 164 +++- iMEGA/Languages/tl.lproj/Localizable.strings | 262 +++-- iMEGA/Languages/tr.lproj/Localizable.strings | 166 +++- iMEGA/Languages/uk.lproj/Localizable.strings | 154 ++- iMEGA/Languages/vi.lproj/Localizable.strings | 162 +++- .../zh-Hans.lproj/Localizable.strings | 162 +++- .../zh-Hant.lproj/Localizable.strings | 156 ++- iMEGA/Links/FileLinkViewController.h | 3 +- iMEGA/Links/FileLinkViewController.m | 36 +- iMEGA/Links/FolderLinkViewController.h | 3 +- iMEGA/Links/FolderLinkViewController.m | 89 +- ...CheckEmailAndFollowTheLinkViewController.m | 76 +- iMEGA/Login/ConfirmAccountViewController.h | 10 +- iMEGA/Login/ConfirmAccountViewController.m | 169 ++-- iMEGA/Login/CreateAccountViewController.m | 408 ++++---- iMEGA/Login/LoginViewController.h | 2 + iMEGA/Login/LoginViewController.m | 182 ++-- iMEGA/Login/Main.storyboard | 696 ++++++------- iMEGA/Login/MainTabBarController.h | 8 +- iMEGA/Login/MainTabBarController.m | 86 +- iMEGA/Login/OnboardingInfoView.swift | 2 +- iMEGA/Login/OnboardingViewController.swift | 2 +- .../AchievementsDetailsViewController.m | 4 +- .../Achievements/AchievementsViewController.m | 6 +- .../InviteFriendsViewController.m | 16 +- .../ReferralBonusesTableViewController.m | 2 +- iMEGA/My Account/MyAccount.storyboard | 4 +- .../My Account/MyAccountBaseViewController.m | 5 +- .../My Account/MyAccountHallViewController.m | 67 +- iMEGA/My Account/MyAccountViewController.m | 2 + .../Notifications/NotificationTableViewCell.h | 17 + .../Notifications/NotificationTableViewCell.m | 12 + .../Notifications/Notifications.storyboard | 119 +++ .../NotificationsTableViewController.h | 10 + .../NotificationsTableViewController.m | 491 ++++++++++ iMEGA/My Account/Upgrade/MEGAPurchase.m | 17 +- .../Upgrade/ProductDetailViewController.m | 9 +- .../Upgrade/UpgradeTableViewController.m | 16 +- iMEGA/Offline/Offline.storyboard | 364 +++++-- .../Offline/OfflineCollectionViewController.h | 18 + .../Offline/OfflineCollectionViewController.m | 277 ++++++ .../Offline/OfflineTableViewViewController.h | 18 + .../Offline/OfflineTableViewViewController.m | 266 +++++ iMEGA/Offline/OfflineViewController.h | 19 + iMEGA/Offline/OfflineViewController.m | 917 +++++++++--------- .../MEGAPhotoBrowserViewController.h | 4 +- .../MEGAPhotoBrowserViewController.m | 57 +- .../MEGAPhotoBrowserViewController.storyboard | 2 +- .../Settings/About/AboutTableViewController.m | 9 +- .../Advanced/AdvancedTableViewController.m | 8 +- iMEGA/Settings/Chat/ChatSettings.storyboard | 84 +- .../Chat/ChatSettingsTableViewController.m | 3 - .../Chat/ChatStatusTableViewController.m | 36 +- .../FileManagementTableViewController.m | 20 +- .../RubbishBinTableViewController.m | 28 +- iMEGA/Settings/Help/HelpTableViewController.m | 8 +- .../ChangePasswordViewController.m | 725 +++++++------- ...ledTwoFactorAuthenticationViewController.m | 2 +- ...ingTwoFactorAuthenticationViewController.m | 4 +- ...oFactorAuthenticationTableViewController.m | 4 +- iMEGA/Settings/Settings.storyboard | 415 ++++---- iMEGA/Settings/SettingsTableViewController.m | 13 +- .../Shared Items/SharedItemsViewController.m | 4 +- iMEGA/Transfers/QueuedTransferItem.h | 13 - iMEGA/Transfers/QueuedTransferItem.m | 17 - iMEGA/Transfers/TransferTableViewCell.h | 11 +- iMEGA/Transfers/TransferTableViewCell.m | 188 ++-- iMEGA/Transfers/Transfers.storyboard | 126 +-- iMEGA/Transfers/TransfersViewController.m | 474 +++++---- .../TwoFactorAuthenticationViewController.m | 6 +- iMEGA/Utils/DevicePermissionsHelper.m | 10 +- iMEGA/Utils/Headers/LayoutView.h | 5 + iMEGA/Utils/Headers/LinkOption.h | 9 + iMEGA/Utils/Headers/MegaNodeActionType.h | 3 +- iMEGA/Utils/Helper.h | 16 +- iMEGA/Utils/Helper.m | 156 +-- iMEGA/Utils/MEGAAVViewController.h | 2 +- iMEGA/Utils/MEGAAVViewController.m | 36 +- iMEGA/Utils/MEGALinkManager.h | 43 + iMEGA/Utils/MEGALinkManager.m | 620 ++++++++++++ iMEGA/Utils/MEGALocalNotificationManager.h | 16 + iMEGA/Utils/MEGALocalNotificationManager.m | 204 ++++ iMEGA/Utils/MEGALogger.h | 3 - iMEGA/Utils/MEGALogger.m | 24 +- iMEGA/Utils/MEGAProcessAsset.m | 40 +- iMEGA/Utils/MEGAReachabilityManager.m | 1 + iMEGA/Utils/MEGASdkManager.m | 14 +- .../CustomActionViewController.m | 28 + .../CustomModalAlertViewController.m | 2 +- .../PasswordReminder.storyboard | 16 +- .../PasswordReminderViewController.m | 2 +- .../TestPasswordViewController.m | 33 +- iMEGA/Utils/Views/InputView.h | 21 + iMEGA/Utils/Views/InputView.m | 75 ++ iMEGA/Utils/Views/InputView.xib | 97 ++ iMEGA/Utils/Views/PasswordView.h | 15 +- iMEGA/Utils/Views/PasswordView.m | 83 +- iMEGA/Utils/Views/PasswordView.xib | 196 ++-- iMEGA/Vendor/CTAssetsPickerController | 2 +- iMEGA/Vendor/JSQMessagesViewController | 2 +- iMEGA/Vendor/Karere | 2 +- iMEGA/Vendor/MGSwipeTableCell | 2 +- iMEGA/Vendor/PieChartView | 2 +- iMEGA/Vendor/PureLayout | 2 +- iMEGA/Vendor/SDK | 2 +- iMEGA/Vendor/SVProgressHUD | 2 +- 253 files changed, 12921 insertions(+), 5997 deletions(-) create mode 100644 iMEGA/API/Chat/MEGAChatNotificationDelegate.h create mode 100644 iMEGA/API/Chat/MEGAChatNotificationDelegate.m create mode 100644 iMEGA/API/Requests/MEGAGenericRequestDelegate.h create mode 100644 iMEGA/API/Requests/MEGAGenericRequestDelegate.m create mode 100644 iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h create mode 100644 iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m create mode 100644 iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h create mode 100644 iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m create mode 100644 iMEGA/Categories/MEGATransferList+MNZCategory.h create mode 100644 iMEGA/Categories/MEGATransferList+MNZCategory.m create mode 100644 iMEGA/Categories/MEGAUserAlertList+MNZCategory.h create mode 100644 iMEGA/Categories/MEGAUserAlertList+MNZCategory.m create mode 100644 iMEGA/Categories/UITextField+MNZCategory.h create mode 100644 iMEGA/Categories/UITextField+MNZCategory.m create mode 100644 iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h create mode 100644 iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m create mode 100644 iMEGA/Cloud drive/CloudDriveCollectionViewController.h create mode 100644 iMEGA/Cloud drive/CloudDriveCollectionViewController.m create mode 100644 iMEGA/Cloud drive/CloudDriveGridFlowLayout.h create mode 100644 iMEGA/Cloud drive/CloudDriveGridFlowLayout.m create mode 100644 iMEGA/Cloud drive/CloudDriveTableViewController.h create mode 100644 iMEGA/Cloud drive/CloudDriveTableViewController.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOFolderLayout+CoreDataClass.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOFolderLayout+CoreDataClass.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOFolderLayout+CoreDataProperties.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOFolderLayout+CoreDataProperties.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOMessage+CoreDataClass.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOMessage+CoreDataClass.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOMessage+CoreDataProperties.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOMessage+CoreDataProperties.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOOfflineFolderLayout+CoreDataClass.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOOfflineFolderLayout+CoreDataClass.m create mode 100644 iMEGA/CoreData/ManagedObjects/MOOfflineFolderLayout+CoreDataProperties.h create mode 100644 iMEGA/CoreData/ManagedObjects/MOOfflineFolderLayout+CoreDataProperties.m rename iMEGA/Images.xcassets/{Login => ChangeEmail}/email.imageset/Contents.json (100%) mode change 100755 => 100644 rename iMEGA/Images.xcassets/{Login => ChangeEmail}/email.imageset/email.pdf (100%) rename iMEGA/Images.xcassets/{Login => ChangeEmail}/errorEmail.imageset/Contents.json (100%) rename iMEGA/Images.xcassets/{Login => ChangeEmail}/errorEmail.imageset/errorEmail.pdf (100%) create mode 100644 iMEGA/Images.xcassets/Empty states/notificationsEmptyState.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/Empty states/notificationsEmptyState.imageset/notificationsEmptyState.pdf create mode 100644 iMEGA/Images.xcassets/Generic/thumbnail_selected.imageset/Contents.json rename iMEGA/Images.xcassets/{Login/name.imageset/name.pdf => Generic/thumbnail_selected.imageset/thumbnail_selected.pdf} (64%) create mode 100644 iMEGA/Images.xcassets/Login/mail.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/Login/mail.imageset/email.pdf create mode 100644 iMEGA/Images.xcassets/Login/name.imageset/profile.pdf create mode 100644 iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/saveToPhotos.pdf create mode 100644 iMEGA/Images.xcassets/User alerts/Contents.json create mode 100644 iMEGA/Images.xcassets/User alerts/Notifications.imageset/Contents.json create mode 100644 iMEGA/Images.xcassets/User alerts/Notifications.imageset/Notifications.pdf create mode 100644 iMEGA/My Account/Notifications/NotificationTableViewCell.h create mode 100644 iMEGA/My Account/Notifications/NotificationTableViewCell.m create mode 100644 iMEGA/My Account/Notifications/Notifications.storyboard create mode 100644 iMEGA/My Account/Notifications/NotificationsTableViewController.h create mode 100644 iMEGA/My Account/Notifications/NotificationsTableViewController.m create mode 100644 iMEGA/Offline/OfflineCollectionViewController.h create mode 100644 iMEGA/Offline/OfflineCollectionViewController.m create mode 100644 iMEGA/Offline/OfflineTableViewViewController.h create mode 100644 iMEGA/Offline/OfflineTableViewViewController.m delete mode 100644 iMEGA/Transfers/QueuedTransferItem.h delete mode 100644 iMEGA/Transfers/QueuedTransferItem.m create mode 100644 iMEGA/Utils/Headers/LayoutView.h create mode 100644 iMEGA/Utils/Headers/LinkOption.h create mode 100644 iMEGA/Utils/MEGALinkManager.h create mode 100644 iMEGA/Utils/MEGALinkManager.m create mode 100644 iMEGA/Utils/MEGALocalNotificationManager.h create mode 100644 iMEGA/Utils/MEGALocalNotificationManager.m create mode 100644 iMEGA/Utils/Views/InputView.h create mode 100644 iMEGA/Utils/Views/InputView.m create mode 100644 iMEGA/Utils/Views/InputView.xib diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index 43907f53ad..6ae2253826 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -170,17 +170,20 @@ 41EAAE4E1D06B49400622FAF /* UIImage+CTAssetsPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41EAAE451D06B49400622FAF /* UIImage+CTAssetsPickerController.m */; }; 41EABF171DA7C5250087271C /* Chat.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41EABF161DA7C5250087271C /* Chat.storyboard */; }; 41EC68461D76DA93003EC803 /* UIBarButtonItem+Badge.m in Sources */ = {isa = PBXBuildFile; fileRef = 41EC68341D76DA93003EC803 /* UIBarButtonItem+Badge.m */; }; - 41EC68551D76E0F6003EC803 /* PieChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = 41EC68541D76E0F6003EC803 /* PieChartView.m */; }; 41FC83B11AA853CC008FA551 /* SettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41FC83B01AA853CC008FA551 /* SettingsTableViewController.m */; }; 41FC83C01AAE056C008FA551 /* CameraUploadsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41FC83BF1AAE056C008FA551 /* CameraUploadsTableViewController.m */; }; 5B0062C61F3DBA16008B7A91 /* MyAccountHallTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0062C51F3DBA16008B7A91 /* MyAccountHallTableViewCell.m */; }; 5B00B6E8210745BF0076D5BB /* FileManagementTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B00B6E7210745BF0076D5BB /* FileManagementTableViewController.m */; }; 5B0C55AE1EE9575E0058CBE1 /* MEGARemoveContactRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0C55AD1EE9575E0058CBE1 /* MEGARemoveContactRequestDelegate.m */; }; + 5B0CD42A2188D71100F97A2C /* UITextField+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CD4292188D71100F97A2C /* UITextField+MNZCategory.m */; }; 5B118EC31FB1B0C6005D7D90 /* NSMutableArray+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B118EC21FB1B0C5005D7D90 /* NSMutableArray+MNZCategory.m */; }; 5B134B8F1F86260B000E367E /* HelpModalViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B134B8E1F86260A000E367E /* HelpModalViewController.xib */; }; 5B14A41520D10043005F8146 /* MEGAMultiFactorAuthCheckRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B14A41320D10042005F8146 /* MEGAMultiFactorAuthCheckRequestDelegate.m */; }; 5B17C9B31F73DE310093F162 /* InviteFriendsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B17C9B21F73DE310093F162 /* InviteFriendsViewController.m */; }; 5B19713D1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B19713C1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m */; }; + 5B1AB4AF20E5407300FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B1AB4AE20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m */; }; + 5B20B00A20E61DDF00511C0F /* MEGALinkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */; }; + 5B20B01120E6300300511C0F /* MEGAGenericRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */; }; 5B1F74D42126E30C00F09A16 /* AwaitingEmailConfirmationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B1F74D22126E30B00F09A16 /* AwaitingEmailConfirmationView.m */; }; 5B1F74D52126E30C00F09A16 /* AwaitingEmailConfirmationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B1F74D32126E30B00F09A16 /* AwaitingEmailConfirmationView.xib */; }; 5B20F4A21EFA9629007E0A34 /* ChatAttachedNodesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20F4A11EFA9629007E0A34 /* ChatAttachedNodesViewController.m */; }; @@ -211,11 +214,13 @@ 5B5A983C212EF29500FDBC79 /* RubbishBinTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5A983A212EF29500FDBC79 /* RubbishBinTableViewController.m */; }; 5B5B4FFF1E681BAC00DBEB3B /* UIFont+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5B4FFE1E681BAC00DBEB3B /* UIFont+MNZCategory.m */; }; 5B5B50011E684FDC00DBEB3B /* SF-UI-Display-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B5B50001E684FDC00DBEB3B /* SF-UI-Display-Regular.otf */; }; + 5B5E591320EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E591220EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m */; }; 5B5FBDDF20BD8CB2000EE35B /* TwoFactorAuthentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B5FBDDE20BD8CB2000EE35B /* TwoFactorAuthentication.storyboard */; }; 5B6429DD2020A8CB000E0DCB /* SF-UI-Display-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429DC2020A5E7000E0DCB /* SF-UI-Display-Semibold.otf */; }; 5B6429E02020A8CF000E0DCB /* SF-UI-Text-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429DB2020A5E6000E0DCB /* SF-UI-Text-Semibold.otf */; }; 5B6429E42020A9A3000E0DCB /* SF-UI-Display-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429E32020A9A2000E0DCB /* SF-UI-Display-Bold.otf */; }; 5B64D4EE2111D5DE004CD155 /* EnabledTwoFactorAuthenticationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B64D4ED2111D5DE004CD155 /* EnabledTwoFactorAuthenticationViewController.m */; }; + 5B6CCF0C21788A3A0049AD14 /* MEGATransferList+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6CCF0B21788A390049AD14 /* MEGATransferList+MNZCategory.m */; }; 5B71631D203D9CE1001E5FBC /* NodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 415226711A692ECC00EC7BB6 /* NodeTableViewCell.m */; }; 5B71631E203D9D28001E5FBC /* NodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 415226711A692ECC00EC7BB6 /* NodeTableViewCell.m */; }; 5B78F0B21F3C5362007ED721 /* MyAccountHallViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B78F0B11F3C5362007ED721 /* MyAccountHallViewController.m */; }; @@ -293,9 +298,15 @@ 7754ED9920B47A2400FF8B7B /* MEGALoginToFolderLinkRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7754ED9820B47A2400FF8B7B /* MEGALoginToFolderLinkRequestDelegate.m */; }; 7754ED9C20B47F1000FF8B7B /* MEGAFetchNodesRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7754ED9B20B47F1000FF8B7B /* MEGAFetchNodesRequestDelegate.m */; }; 775652D7203AD2FC009E7EC8 /* ContactLinkQRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 775652D6203AD2FC009E7EC8 /* ContactLinkQRViewController.m */; }; + 77597006219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 77597005219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.m */; }; + 7759700A219EB85300B31671 /* NotificationsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77597009219EB85300B31671 /* NotificationsTableViewController.m */; }; + 7759700C219EBCE700B31671 /* Notifications.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7759700B219EBCE700B31671 /* Notifications.storyboard */; }; + 7759700F219EC2D700B31671 /* NotificationTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7759700E219EC2D700B31671 /* NotificationTableViewCell.m */; }; 775D9C0B20EA0D49001BF1E8 /* ShareAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */; }; 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */; }; 7776472D2158D45900EC7F4C /* String+MNZExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */; }; + 777615AF2175D87D00A7796D /* InputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 777615AE2175D87D00A7796D /* InputView.m */; }; + 777615B12175D88C00A7796D /* InputView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 777615B02175D88C00A7796D /* InputView.xib */; }; 777E9229203EB0E200266229 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 777E9228203EB0E100266229 /* CoreImage.framework */; }; 777E922D203EE14B00266229 /* QRSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 777E922C203EE14B00266229 /* QRSettingsTableViewController.m */; }; 77892E9B20E4FFBA0020A533 /* Chat.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41EABF161DA7C5250087271C /* Chat.storyboard */; }; @@ -331,7 +342,16 @@ 77F95CF120E3E5EB00F42D9C /* MEGAChatCreateChatGroupRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A815FCE31FE1795C0075AC29 /* MEGAChatCreateChatGroupRequestDelegate.m */; }; 77F95CF220E3E5F800F42D9C /* MEGAChatBaseRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A802B1F91FAB293700AC8BB0 /* MEGAChatBaseRequestDelegate.m */; }; 77F95CF320E3E60A00F42D9C /* MEGAChatAttachNodeRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B222A73205A9CD40083D433 /* MEGAChatAttachNodeRequestDelegate.m */; }; + 830B548A2178845300BE1E1F /* CloudDriveTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 830B54892178845300BE1E1F /* CloudDriveTableViewController.m */; }; 830EF9A420062F3800FAA947 /* DevicePermissionsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 830EF9A320062F3800FAA947 /* DevicePermissionsHelper.m */; }; + 8310488A21AECD860057400D /* DTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 413199691BD7BD5D002CD6EA /* DTConstants.m */; }; + 8310488B21AECDBA0057400D /* DTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 413199691BD7BD5D002CD6EA /* DTConstants.m */; }; + 831D61A5218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A3218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m */; }; + 831D61A6218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A4218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m */; }; + 831D61A7218738EB0038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A3218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m */; }; + 831D61A8218738EC0038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A3218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m */; }; + 831D61A9218738F30038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A4218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m */; }; + 831D61AA218738F40038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 831D61A4218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m */; }; 831FBEA220EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 831FBEA120EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.m */; }; 832E1025202B09AE00BDD30F /* NodeActionCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 832E1023202B09AE00BDD30F /* NodeActionCollectionViewCell.m */; }; 832E1026202B09AE00BDD30F /* NodeActionCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 832E1024202B09AE00BDD30F /* NodeActionCollectionViewCell.xib */; }; @@ -346,9 +366,9 @@ 832F19EE2091BD8E0067679E /* MOMediaDestination+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 832F19E72091BA970067679E /* MOMediaDestination+CoreDataProperties.m */; }; 832F19EF2091BD980067679E /* MOMediaDestination+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 832F19EA2091BA970067679E /* MOMediaDestination+CoreDataClass.m */; }; 832F19F02091BD980067679E /* MOMediaDestination+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 832F19EA2091BA970067679E /* MOMediaDestination+CoreDataClass.m */; }; - 832FC4452130069100740123 /* QueuedTransferItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 832FC4442130069100740123 /* QueuedTransferItem.m */; }; 83337CAB21009494000F40A7 /* ItemListModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 83337CAA21009494000F40A7 /* ItemListModel.m */; }; 83337CAC2100B33D000F40A7 /* ItemListModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 83337CAA21009494000F40A7 /* ItemListModel.m */; }; + 83369CE42179D0A0002B09C3 /* CloudDriveCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83369CE32179D0A0002B09C3 /* CloudDriveCollectionViewController.m */; }; 834AD6052024694200D2527F /* MGSwipeButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 834AD6012024694200D2527F /* MGSwipeButton.m */; }; 834AD6062024694200D2527F /* MGSwipeTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 834AD6032024694200D2527F /* MGSwipeTableCell.m */; }; 834AD6072024697200D2527F /* MGSwipeButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 834AD6012024694200D2527F /* MGSwipeButton.m */; }; @@ -379,9 +399,21 @@ 837A88BF20B42290007B55D1 /* CloudDriveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837A88BE20B42290007B55D1 /* CloudDriveViewController.m */; }; 837A88C220B44AA8007B55D1 /* OfflineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837A88C120B44AA8007B55D1 /* OfflineViewController.m */; }; 837A88C520B45C11007B55D1 /* SendToViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837A88C420B45C11007B55D1 /* SendToViewController.m */; }; + 839D5A9721C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D5A9621C25A7D00FAE757 /* CloudDriveGridFlowLayout.m */; }; + 839D5A9821C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D5A9621C25A7D00FAE757 /* CloudDriveGridFlowLayout.m */; }; + 839D5A9921C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D5A9621C25A7D00FAE757 /* CloudDriveGridFlowLayout.m */; }; 83B841672051905E00E5AC69 /* MEGAGetThumbnailRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A820DA3B1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m */; }; 83B841682051913C00E5AC69 /* MEGAGetThumbnailRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A820DA3B1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m */; }; 83D64E2D20178A0400E24155 /* PasswordReminder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 83D64E2C20178A0400E24155 /* PasswordReminder.storyboard */; }; + 83E076F52179D7BC00058409 /* NodeCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E076F42179D7BC00058409 /* NodeCollectionViewCell.m */; }; + 83FB1431219AC7CD00418BE6 /* OfflineTableViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1430219AC7CD00418BE6 /* OfflineTableViewViewController.m */; }; + 83FB1434219AC7DD00418BE6 /* OfflineCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1433219AC7DD00418BE6 /* OfflineCollectionViewController.m */; }; + 83FB1439219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1437219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m */; }; + 83FB143A219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1438219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m */; }; + 83FB143B219C3C6D00418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1438219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m */; }; + 83FB143C219C3C6E00418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1438219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m */; }; + 83FB143D219C3C7100418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1437219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m */; }; + 83FB143E219C3C7100418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FB1437219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m */; }; 9411354F1F00FD1500D33428 /* Cloud.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 415226691A692ECC00EC7BB6 /* Cloud.storyboard */; }; 941135501F00FFAB00D33428 /* BrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AB8EC01A7103B300E40A39 /* BrowserViewController.m */; }; 941135511F00FFC700D33428 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 415226881A692ECC00EC7BB6 /* Helper.m */; }; @@ -629,6 +661,7 @@ A89E77491EA605A900ADC0D2 /* MEGAMessageAttachmentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A89E77481EA605A900ADC0D2 /* MEGAMessageAttachmentView.xib */; }; A8AA4F7A1ED42B9E00E7BA23 /* MEGALoginRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8AA4F781ED42B9E00E7BA23 /* MEGALoginRequestDelegate.m */; }; A8AA4F7D1ED433CA00E7BA23 /* MEGABaseRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8AA4F7C1ED433CA00E7BA23 /* MEGABaseRequestDelegate.m */; }; + A8B354D421354680002018A9 /* MEGAChatNotificationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8B354D321354680002018A9 /* MEGAChatNotificationDelegate.m */; }; A8C262901FA0B3F3006172CE /* librtc_sdk_objc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8F6E88B1F9E75090087859C /* librtc_sdk_objc.a */; }; A8C262911FA0B3F3006172CE /* libwebsockets.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8F6E88E1F9E75B60087859C /* libwebsockets.a */; }; A8C262921FA0B425006172CE /* librtc_sdk_objc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8F6E88B1F9E75090087859C /* librtc_sdk_objc.a */; }; @@ -650,8 +683,14 @@ A8C262AD1FA1D85E006172CE /* MEGARemoteImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C262AC1FA1D85E006172CE /* MEGARemoteImageView.m */; }; A8C262B01FA1D876006172CE /* MEGALocalImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C262AF1FA1D876006172CE /* MEGALocalImageView.m */; }; A8EC55B41F02AD1D00426DCC /* MEGAGetPreviewRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8EC55B31F02AD1D00426DCC /* MEGAGetPreviewRequestDelegate.m */; }; + A8F0A1C7217898F600575C57 /* MEGALocalNotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F0A1C6217898F600575C57 /* MEGALocalNotificationManager.m */; }; + A8F0A1D82178A73300575C57 /* MOMessage+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F0A1D42178A73300575C57 /* MOMessage+CoreDataClass.m */; }; + A8F0A1D92178A73300575C57 /* MOMessage+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F0A1D72178A73300575C57 /* MOMessage+CoreDataProperties.m */; }; + A8F0A1DA2178B41900575C57 /* MOMessage+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F0A1D42178A73300575C57 /* MOMessage+CoreDataClass.m */; }; + A8F0A1DC2178B43300575C57 /* MOMessage+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F0A1D42178A73300575C57 /* MOMessage+CoreDataClass.m */; }; A8F2629A1FA8F7C3000B2768 /* incoming_voice_video_call.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A8F262991FA8F7C2000B2768 /* incoming_voice_video_call.mp3 */; }; A8F2629C1FA8F82F000B2768 /* hang_out.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A8F2629B1FA8F82F000B2768 /* hang_out.mp3 */; }; + A8F43939218726690054472A /* PieChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F43937218726690054472A /* PieChartView.m */; }; A8F5D67A1ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F5D6791ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m */; }; A8F5D67D1ED5B0990087DA40 /* MEGASendSignupLinkRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F5D67C1ED5B0990087DA40 /* MEGASendSignupLinkRequestDelegate.m */; }; A8F6C5F31FAB3FD3001E274D /* MEGAChatEnableDisableAudioRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F6C5F11FAB3FD3001E274D /* MEGAChatEnableDisableAudioRequestDelegate.m */; }; @@ -1083,8 +1122,6 @@ 41EABF161DA7C5250087271C /* Chat.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Chat.storyboard; path = Chat/Chat.storyboard; sourceTree = ""; }; 41EC68331D76DA93003EC803 /* UIBarButtonItem+Badge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+Badge.h"; sourceTree = ""; }; 41EC68341D76DA93003EC803 /* UIBarButtonItem+Badge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+Badge.m"; sourceTree = ""; }; - 41EC68531D76E0F6003EC803 /* PieChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PieChartView.h; path = Vendor/PieChartView/PieChartDemo/PieChartView.h; sourceTree = ""; }; - 41EC68541D76E0F6003EC803 /* PieChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PieChartView.m; path = Vendor/PieChartView/PieChartDemo/PieChartView.m; sourceTree = ""; }; 41FC83AF1AA853CC008FA551 /* SettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsTableViewController.h; sourceTree = ""; }; 41FC83B01AA853CC008FA551 /* SettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SettingsTableViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 41FC83BE1AAE056C008FA551 /* CameraUploadsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CameraUploadsTableViewController.h; sourceTree = ""; }; @@ -1095,6 +1132,8 @@ 5B00B6E7210745BF0076D5BB /* FileManagementTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileManagementTableViewController.m; sourceTree = ""; }; 5B0C55AC1EE9575E0058CBE1 /* MEGARemoveContactRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGARemoveContactRequestDelegate.h; path = API/Requests/MEGARemoveContactRequestDelegate.h; sourceTree = ""; }; 5B0C55AD1EE9575E0058CBE1 /* MEGARemoveContactRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGARemoveContactRequestDelegate.m; path = API/Requests/MEGARemoveContactRequestDelegate.m; sourceTree = ""; }; + 5B0CD4282188D71100F97A2C /* UITextField+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+MNZCategory.h"; sourceTree = ""; }; + 5B0CD4292188D71100F97A2C /* UITextField+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+MNZCategory.m"; sourceTree = ""; }; 5B118EC11FB1B0C5005D7D90 /* NSMutableArray+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+MNZCategory.h"; sourceTree = ""; }; 5B118EC21FB1B0C5005D7D90 /* NSMutableArray+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+MNZCategory.m"; sourceTree = ""; }; 5B134B8E1F86260A000E367E /* HelpModalViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HelpModalViewController.xib; sourceTree = ""; }; @@ -1104,6 +1143,12 @@ 5B17C9B21F73DE310093F162 /* InviteFriendsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InviteFriendsViewController.m; sourceTree = ""; }; 5B19713B1F2F1D1C00DE1BB2 /* RemoveSharingActivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoveSharingActivity.h; sourceTree = ""; }; 5B19713C1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoveSharingActivity.m; sourceTree = ""; }; + 5B1AB4AD20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAQuerySignupLinkRequestDelegate.h; path = API/Requests/MEGAQuerySignupLinkRequestDelegate.h; sourceTree = ""; }; + 5B1AB4AE20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAQuerySignupLinkRequestDelegate.m; path = API/Requests/MEGAQuerySignupLinkRequestDelegate.m; sourceTree = ""; }; + 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGALinkManager.m; sourceTree = ""; }; + 5B20B00B20E61DFB00511C0F /* MEGALinkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGALinkManager.h; sourceTree = ""; }; + 5B20B00F20E6300300511C0F /* MEGAGenericRequestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MEGAGenericRequestDelegate.h; path = API/Requests/MEGAGenericRequestDelegate.h; sourceTree = ""; }; + 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MEGAGenericRequestDelegate.m; path = API/Requests/MEGAGenericRequestDelegate.m; sourceTree = ""; }; 5B1F74D12126E30B00F09A16 /* AwaitingEmailConfirmationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AwaitingEmailConfirmationView.h; sourceTree = ""; }; 5B1F74D22126E30B00F09A16 /* AwaitingEmailConfirmationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AwaitingEmailConfirmationView.m; sourceTree = ""; }; 5B1F74D32126E30B00F09A16 /* AwaitingEmailConfirmationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AwaitingEmailConfirmationView.xib; sourceTree = ""; }; @@ -1150,12 +1195,16 @@ 5B5B4FFD1E681BAC00DBEB3B /* UIFont+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+MNZCategory.h"; sourceTree = ""; }; 5B5B4FFE1E681BAC00DBEB3B /* UIFont+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+MNZCategory.m"; sourceTree = ""; }; 5B5B50001E684FDC00DBEB3B /* SF-UI-Display-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-UI-Display-Regular.otf"; path = "Fonts/SF-UI-Display-Regular.otf"; sourceTree = ""; }; + 5B5E591120EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAQueryRecoveryLinkRequestDelegate.h; path = API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h; sourceTree = ""; }; + 5B5E591220EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAQueryRecoveryLinkRequestDelegate.m; path = API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m; sourceTree = ""; }; 5B5FBDDE20BD8CB2000EE35B /* TwoFactorAuthentication.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = TwoFactorAuthentication.storyboard; sourceTree = ""; }; 5B6429DB2020A5E6000E0DCB /* SF-UI-Text-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-UI-Text-Semibold.otf"; path = "Fonts/SF-UI-Text-Semibold.otf"; sourceTree = ""; }; 5B6429DC2020A5E7000E0DCB /* SF-UI-Display-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-UI-Display-Semibold.otf"; path = "Fonts/SF-UI-Display-Semibold.otf"; sourceTree = ""; }; 5B6429E32020A9A2000E0DCB /* SF-UI-Display-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-UI-Display-Bold.otf"; path = "Fonts/SF-UI-Display-Bold.otf"; sourceTree = ""; }; 5B64D4EC2111D5DE004CD155 /* EnabledTwoFactorAuthenticationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EnabledTwoFactorAuthenticationViewController.h; sourceTree = ""; }; 5B64D4ED2111D5DE004CD155 /* EnabledTwoFactorAuthenticationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EnabledTwoFactorAuthenticationViewController.m; sourceTree = ""; }; + 5B6CCF0A21788A390049AD14 /* MEGATransferList+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MEGATransferList+MNZCategory.h"; sourceTree = ""; }; + 5B6CCF0B21788A390049AD14 /* MEGATransferList+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MEGATransferList+MNZCategory.m"; sourceTree = ""; }; 5B72A14B20D3A69C007FE4FD /* URLType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = URLType.h; sourceTree = ""; }; 5B78F0B01F3C5362007ED721 /* MyAccountHallViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyAccountHallViewController.h; sourceTree = ""; }; 5B78F0B11F3C5362007ED721 /* MyAccountHallViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyAccountHallViewController.m; sourceTree = ""; }; @@ -1187,6 +1236,7 @@ 5BD5CCDC1EB9DA4800175A25 /* ChangeNameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChangeNameViewController.m; sourceTree = ""; }; 5BDE180A1F500DEE00CD9F82 /* MyAccountBaseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyAccountBaseViewController.h; sourceTree = ""; }; 5BDE180B1F500DEE00CD9F82 /* MyAccountBaseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyAccountBaseViewController.m; sourceTree = ""; }; + 5BE08A0820F4DB0100950D30 /* LinkOption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LinkOption.h; sourceTree = ""; }; 5BE484BE215BB3CD003B291D /* ChatSettings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ChatSettings.storyboard; sourceTree = ""; }; 5BF4B3B520AB081A00A6E6D7 /* TwoFactorAuthenticationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TwoFactorAuthenticationViewController.h; sourceTree = ""; }; 5BF4B3B620AB081A00A6E6D7 /* TwoFactorAuthenticationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TwoFactorAuthenticationViewController.m; sourceTree = ""; }; @@ -1218,10 +1268,20 @@ 7754ED9B20B47F1000FF8B7B /* MEGAFetchNodesRequestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MEGAFetchNodesRequestDelegate.m; path = API/Requests/MEGAFetchNodesRequestDelegate.m; sourceTree = ""; }; 775652D5203AD2FC009E7EC8 /* ContactLinkQRViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContactLinkQRViewController.h; sourceTree = ""; }; 775652D6203AD2FC009E7EC8 /* ContactLinkQRViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContactLinkQRViewController.m; sourceTree = ""; }; + 77597004219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGAUserAlertList+MNZCategory.h"; sourceTree = ""; }; + 77597005219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MEGAUserAlertList+MNZCategory.m"; sourceTree = ""; }; + 77597008219EB85300B31671 /* NotificationsTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationsTableViewController.h; sourceTree = ""; }; + 77597009219EB85300B31671 /* NotificationsTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationsTableViewController.m; sourceTree = ""; }; + 7759700B219EBCE700B31671 /* Notifications.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Notifications.storyboard; sourceTree = ""; }; + 7759700D219EC2D700B31671 /* NotificationTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationTableViewCell.h; sourceTree = ""; }; + 7759700E219EC2D700B31671 /* NotificationTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationTableViewCell.m; sourceTree = ""; }; 775D9C0920EA0D49001BF1E8 /* ShareAttachment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareAttachment.h; sourceTree = ""; }; 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareAttachment.m; sourceTree = ""; }; 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingInfoView.swift; sourceTree = ""; }; 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+MNZExtension.swift"; sourceTree = ""; }; + 777615AD2175D87D00A7796D /* InputView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InputView.h; sourceTree = ""; }; + 777615AE2175D87D00A7796D /* InputView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InputView.m; sourceTree = ""; }; + 777615B02175D88C00A7796D /* InputView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputView.xib; sourceTree = ""; }; 777E9228203EB0E100266229 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; 777E922B203EE14B00266229 /* QRSettingsTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QRSettingsTableViewController.h; sourceTree = ""; }; 777E922C203EE14B00266229 /* QRSettingsTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QRSettingsTableViewController.m; sourceTree = ""; }; @@ -1258,8 +1318,15 @@ 77F43DD82034637300AE4F42 /* UICollectionView+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+MNZCategory.m"; sourceTree = ""; }; 77F95CED20E3B14D00F42D9C /* ShareFilesDestinationTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareFilesDestinationTableViewController.h; sourceTree = ""; }; 77F95CEE20E3B14D00F42D9C /* ShareFilesDestinationTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareFilesDestinationTableViewController.m; sourceTree = ""; }; + 83064D88217F207000FA3DAC /* LayoutView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LayoutView.h; sourceTree = ""; }; + 830B54882178845300BE1E1F /* CloudDriveTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CloudDriveTableViewController.h; sourceTree = ""; }; + 830B54892178845300BE1E1F /* CloudDriveTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CloudDriveTableViewController.m; sourceTree = ""; }; 830EF9A220062F3800FAA947 /* DevicePermissionsHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DevicePermissionsHelper.h; sourceTree = ""; }; 830EF9A320062F3800FAA947 /* DevicePermissionsHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DevicePermissionsHelper.m; sourceTree = ""; }; + 831D61A1218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MOFolderLayout+CoreDataProperties.h"; sourceTree = ""; }; + 831D61A2218738D20038F5B8 /* MOFolderLayout+CoreDataClass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MOFolderLayout+CoreDataClass.h"; sourceTree = ""; }; + 831D61A3218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MOFolderLayout+CoreDataClass.m"; sourceTree = ""; }; + 831D61A4218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MOFolderLayout+CoreDataProperties.m"; sourceTree = ""; }; 831FBEA020EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAArchiveChatRequestDelegate.h; sourceTree = ""; }; 831FBEA120EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGAArchiveChatRequestDelegate.m; sourceTree = ""; }; 832E1022202B09AE00BDD30F /* NodeActionCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NodeActionCollectionViewCell.h; sourceTree = ""; }; @@ -1278,10 +1345,10 @@ 832F19E82091BA970067679E /* MOMediaDestination+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MOMediaDestination+CoreDataClass.h"; sourceTree = ""; }; 832F19E92091BA970067679E /* MOMediaDestination+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MOMediaDestination+CoreDataProperties.h"; sourceTree = ""; }; 832F19EA2091BA970067679E /* MOMediaDestination+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MOMediaDestination+CoreDataClass.m"; sourceTree = ""; }; - 832FC4432130069000740123 /* QueuedTransferItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QueuedTransferItem.h; sourceTree = ""; }; - 832FC4442130069100740123 /* QueuedTransferItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueuedTransferItem.m; sourceTree = ""; }; 83337CA921009494000F40A7 /* ItemListModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ItemListModel.h; sourceTree = ""; }; 83337CAA21009494000F40A7 /* ItemListModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ItemListModel.m; sourceTree = ""; }; + 83369CE22179D0A0002B09C3 /* CloudDriveCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CloudDriveCollectionViewController.h; sourceTree = ""; }; + 83369CE32179D0A0002B09C3 /* CloudDriveCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CloudDriveCollectionViewController.m; sourceTree = ""; }; 834AD6012024694200D2527F /* MGSwipeButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGSwipeButton.m; sourceTree = ""; }; 834AD6022024694200D2527F /* MGSwipeTableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGSwipeTableCell.h; sourceTree = ""; }; 834AD6032024694200D2527F /* MGSwipeTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGSwipeTableCell.m; sourceTree = ""; }; @@ -1326,7 +1393,19 @@ 837A88C120B44AA8007B55D1 /* OfflineViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OfflineViewController.m; sourceTree = ""; }; 837A88C320B45C11007B55D1 /* SendToViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SendToViewController.h; path = Chat/SendToViewController.h; sourceTree = ""; }; 837A88C420B45C11007B55D1 /* SendToViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SendToViewController.m; path = Chat/SendToViewController.m; sourceTree = ""; }; + 839D5A9521C25A7D00FAE757 /* CloudDriveGridFlowLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CloudDriveGridFlowLayout.h; sourceTree = ""; }; + 839D5A9621C25A7D00FAE757 /* CloudDriveGridFlowLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CloudDriveGridFlowLayout.m; sourceTree = ""; }; 83D64E2C20178A0400E24155 /* PasswordReminder.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PasswordReminder.storyboard; sourceTree = ""; }; + 83E076F32179D7BC00058409 /* NodeCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NodeCollectionViewCell.h; sourceTree = ""; }; + 83E076F42179D7BC00058409 /* NodeCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NodeCollectionViewCell.m; sourceTree = ""; }; + 83FB142F219AC7CD00418BE6 /* OfflineTableViewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OfflineTableViewViewController.h; sourceTree = ""; }; + 83FB1430219AC7CD00418BE6 /* OfflineTableViewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OfflineTableViewViewController.m; sourceTree = ""; }; + 83FB1432219AC7DD00418BE6 /* OfflineCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OfflineCollectionViewController.h; sourceTree = ""; }; + 83FB1433219AC7DD00418BE6 /* OfflineCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OfflineCollectionViewController.m; sourceTree = ""; }; + 83FB1435219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MOOfflineFolderLayout+CoreDataProperties.h"; sourceTree = ""; }; + 83FB1436219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MOOfflineFolderLayout+CoreDataClass.h"; sourceTree = ""; }; + 83FB1437219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MOOfflineFolderLayout+CoreDataClass.m"; sourceTree = ""; }; + 83FB1438219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MOOfflineFolderLayout+CoreDataProperties.m"; sourceTree = ""; }; 9419B6BE1F20D1D700FEBE31 /* MEGAIndexer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAIndexer.h; path = Spotlight/MEGAIndexer.h; sourceTree = ""; }; 9419B6BF1F20D1D700FEBE31 /* MEGAIndexer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAIndexer.m; path = Spotlight/MEGAIndexer.m; sourceTree = ""; }; 9419B6C51F20DC5700FEBE31 /* CoreSpotlight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSpotlight.framework; path = System/Library/Frameworks/CoreSpotlight.framework; sourceTree = SDKROOT; }; @@ -1477,6 +1556,8 @@ A8AA4F7B1ED433CA00E7BA23 /* MEGABaseRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGABaseRequestDelegate.h; path = API/Requests/MEGABaseRequestDelegate.h; sourceTree = ""; }; A8AA4F7C1ED433CA00E7BA23 /* MEGABaseRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGABaseRequestDelegate.m; path = API/Requests/MEGABaseRequestDelegate.m; sourceTree = ""; }; A8B1F4701F0A9A080057520C /* MEGAPhotoMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAPhotoMediaItem.h; sourceTree = ""; }; + A8B354D221354680002018A9 /* MEGAChatNotificationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAChatNotificationDelegate.h; sourceTree = ""; }; + A8B354D321354680002018A9 /* MEGAChatNotificationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGAChatNotificationDelegate.m; sourceTree = ""; }; A8C262971FA0B4A2006172CE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; A8C2629A1FA0B4EA006172CE /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; A8C262A31FA0B5CD006172CE /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; @@ -1489,8 +1570,16 @@ A8C262AF1FA1D876006172CE /* MEGALocalImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGALocalImageView.m; sourceTree = ""; }; A8EC55B21F02AD1D00426DCC /* MEGAGetPreviewRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAGetPreviewRequestDelegate.h; path = API/Requests/MEGAGetPreviewRequestDelegate.h; sourceTree = ""; }; A8EC55B31F02AD1D00426DCC /* MEGAGetPreviewRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAGetPreviewRequestDelegate.m; path = API/Requests/MEGAGetPreviewRequestDelegate.m; sourceTree = ""; }; + A8F0A1C5217898F600575C57 /* MEGALocalNotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGALocalNotificationManager.h; sourceTree = ""; }; + A8F0A1C6217898F600575C57 /* MEGALocalNotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGALocalNotificationManager.m; sourceTree = ""; }; + A8F0A1D42178A73300575C57 /* MOMessage+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MOMessage+CoreDataClass.m"; sourceTree = ""; }; + A8F0A1D52178A73300575C57 /* MOMessage+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MOMessage+CoreDataProperties.h"; sourceTree = ""; }; + A8F0A1D62178A73300575C57 /* MOMessage+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MOMessage+CoreDataClass.h"; sourceTree = ""; }; + A8F0A1D72178A73300575C57 /* MOMessage+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MOMessage+CoreDataProperties.m"; sourceTree = ""; }; A8F262991FA8F7C2000B2768 /* incoming_voice_video_call.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = incoming_voice_video_call.mp3; sourceTree = ""; }; A8F2629B1FA8F82F000B2768 /* hang_out.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = hang_out.mp3; sourceTree = ""; }; + A8F43937218726690054472A /* PieChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PieChartView.m; sourceTree = ""; }; + A8F43938218726690054472A /* PieChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PieChartView.h; sourceTree = ""; }; A8F5D6781ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CheckEmailAndFollowTheLinkViewController.h; sourceTree = ""; }; A8F5D6791ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CheckEmailAndFollowTheLinkViewController.m; sourceTree = ""; }; A8F5D67B1ED5B0990087DA40 /* MEGASendSignupLinkRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGASendSignupLinkRequestDelegate.h; path = API/Requests/MEGASendSignupLinkRequestDelegate.h; sourceTree = ""; }; @@ -1951,6 +2040,12 @@ 83654A59203C3E4100B5E426 /* NodeVersionsViewController.m */, 836B956D2063EADE003E279B /* SearchInPdfViewController.h */, 836B956E2063EADE003E279B /* SearchInPdfViewController.m */, + 830B54882178845300BE1E1F /* CloudDriveTableViewController.h */, + 830B54892178845300BE1E1F /* CloudDriveTableViewController.m */, + 83369CE22179D0A0002B09C3 /* CloudDriveCollectionViewController.h */, + 83369CE32179D0A0002B09C3 /* CloudDriveCollectionViewController.m */, + 839D5A9521C25A7D00FAE757 /* CloudDriveGridFlowLayout.h */, + 839D5A9621C25A7D00FAE757 /* CloudDriveGridFlowLayout.m */, ); path = "Cloud drive"; sourceTree = ""; @@ -2005,6 +2100,10 @@ 837A88C120B44AA8007B55D1 /* OfflineViewController.m */, E8E6CADC1A9C8C23008A0CD2 /* OfflineTableViewCell.h */, E8E6CADD1A9C8C23008A0CD2 /* OfflineTableViewCell.m */, + 83FB142F219AC7CD00418BE6 /* OfflineTableViewViewController.h */, + 83FB1430219AC7CD00418BE6 /* OfflineTableViewViewController.m */, + 83FB1432219AC7DD00418BE6 /* OfflineCollectionViewController.h */, + 83FB1433219AC7DD00418BE6 /* OfflineCollectionViewController.m */, ); path = Offline; sourceTree = ""; @@ -2052,12 +2151,16 @@ 41A4204E1C4EA02C002E192E /* MEGALogger.m */, 418072781AA5BBA00078D950 /* MEGAReachabilityManager.h */, 418072791AA5BBA00078D950 /* MEGAReachabilityManager.m */, + 5B20B00B20E61DFB00511C0F /* MEGALinkManager.h */, + 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */, 415226891A692ECC00EC7BB6 /* MEGASdkManager.h */, 4152268A1A692ECC00EC7BB6 /* MEGASdkManager.m */, A81AD7371F3099F800CA4059 /* MEGAProcessAsset.h */, A81AD7381F3099F800CA4059 /* MEGAProcessAsset.m */, 830EF9A220062F3800FAA947 /* DevicePermissionsHelper.h */, 830EF9A320062F3800FAA947 /* DevicePermissionsHelper.m */, + A8F0A1C5217898F600575C57 /* MEGALocalNotificationManager.h */, + A8F0A1C6217898F600575C57 /* MEGALocalNotificationManager.m */, ); path = Utils; sourceTree = ""; @@ -2456,11 +2559,17 @@ 949086871FD5482E002B12BD /* NSAttributedString+MNZCategory.m */, 7720C5B3206A931E00F995D1 /* MEGATransfer+MNZCategory.h */, 7720C5B4206A931E00F995D1 /* MEGATransfer+MNZCategory.m */, + 5B6CCF0A21788A390049AD14 /* MEGATransferList+MNZCategory.h */, + 5B6CCF0B21788A390049AD14 /* MEGATransferList+MNZCategory.m */, 77F43DD72034637300AE4F42 /* UICollectionView+MNZCategory.h */, 77F43DD82034637300AE4F42 /* UICollectionView+MNZCategory.m */, 77B3227D20A436D60037FA89 /* NSURL+MNZCategory.h */, 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */, 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */, + 5B0CD4282188D71100F97A2C /* UITextField+MNZCategory.h */, + 5B0CD4292188D71100F97A2C /* UITextField+MNZCategory.m */, + 77597004219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.h */, + 77597005219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.m */, ); path = Categories; sourceTree = ""; @@ -2488,6 +2597,18 @@ 41D1E0B81B58F0B800982830 /* ManagedObjects */ = { isa = PBXGroup; children = ( + 83FB1435219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.h */, + 83FB1436219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.h */, + 83FB1437219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m */, + 83FB1438219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m */, + 831D61A1218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.h */, + 831D61A2218738D20038F5B8 /* MOFolderLayout+CoreDataClass.h */, + 831D61A3218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m */, + 831D61A4218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m */, + A8F0A1D52178A73300575C57 /* MOMessage+CoreDataProperties.h */, + A8F0A1D62178A73300575C57 /* MOMessage+CoreDataClass.h */, + A8F0A1D42178A73300575C57 /* MOMessage+CoreDataClass.m */, + A8F0A1D72178A73300575C57 /* MOMessage+CoreDataProperties.m */, 8361358C21074C5D002FA3CC /* MOUploadTransfer+CoreDataProperties.h */, 8361358D21074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.h */, 8361358E21074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.m */, @@ -2688,10 +2809,11 @@ 41EC68561D76E101003EC803 /* PieChartView */ = { isa = PBXGroup; children = ( - 41EC68531D76E0F6003EC803 /* PieChartView.h */, - 41EC68541D76E0F6003EC803 /* PieChartView.m */, + A8F43938218726690054472A /* PieChartView.h */, + A8F43937218726690054472A /* PieChartView.m */, ); name = PieChartView; + path = Vendor/PieChartView/JustPieChart/JustPieChart/Classes; sourceTree = ""; }; 41FC83BC1AAE0559008FA551 /* Camera Uploads */ = { @@ -2745,10 +2867,12 @@ 5B2417E720527FB800C8DDE2 /* DisplayMode.h */, 5BC4913020AC684500F7F952 /* TwoFactorAuthentication.h */, 83681574202AFA1700CA8EFC /* MegaNodeActionType.h */, + 5BE08A0820F4DB0100950D30 /* LinkOption.h */, 773DF39A20EE1470004C3418 /* SendMode.h */, 773DF39B20EE14C1004C3418 /* ShareAttachmentType.h */, 5B72A14B20D3A69C007FE4FD /* URLType.h */, 836A987C20F8C8DA00EC719B /* ChatRoomsType.h */, + 83064D88217F207000FA3DAC /* LayoutView.h */, ); path = Headers; sourceTree = ""; @@ -2814,6 +2938,9 @@ 836AEDE32018AD1F00868FC7 /* PasswordView.h */, 836AEDE42018AD1F00868FC7 /* PasswordView.m */, 836AEDE12018AD0C00868FC7 /* PasswordView.xib */, + 777615AD2175D87D00A7796D /* InputView.h */, + 777615AE2175D87D00A7796D /* InputView.m */, + 777615B02175D88C00A7796D /* InputView.xib */, ); path = Views; sourceTree = ""; @@ -2853,6 +2980,18 @@ path = Network; sourceTree = ""; }; + 77597007219EB7FD00B31671 /* Notifications */ = { + isa = PBXGroup; + children = ( + 7759700B219EBCE700B31671 /* Notifications.storyboard */, + 77597008219EB85300B31671 /* NotificationsTableViewController.h */, + 77597009219EB85300B31671 /* NotificationsTableViewController.m */, + 7759700D219EC2D700B31671 /* NotificationTableViewCell.h */, + 7759700E219EC2D700B31671 /* NotificationTableViewCell.m */, + ); + path = Notifications; + sourceTree = ""; + }; 775D9C0820EA0CF3001BF1E8 /* Model */ = { isa = PBXGroup; children = ( @@ -2911,6 +3050,8 @@ 832E1031202D8E6900BDD30F /* NodePropertyTableViewCell.m */, 832E1033202DE10500BDD30F /* NodeTappablePropertyTableViewCell.h */, 832E1034202DE10500BDD30F /* NodeTappablePropertyTableViewCell.m */, + 83E076F32179D7BC00058409 /* NodeCollectionViewCell.h */, + 83E076F42179D7BC00058409 /* NodeCollectionViewCell.m */, ); path = Cells; sourceTree = ""; @@ -3086,6 +3227,8 @@ 836E74C32099DC82005E2317 /* MEGAChatChangeGroupNameRequestDelegate.m */, 831FBEA020EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.h */, 831FBEA120EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.m */, + A8B354D221354680002018A9 /* MEGAChatNotificationDelegate.h */, + A8B354D321354680002018A9 /* MEGAChatNotificationDelegate.m */, ); name = Chat; path = API/Chat; @@ -3211,6 +3354,12 @@ 5B7B4CA11EE5AC1200DA6A8D /* MEGAShareRequestDelegate.m */, 8364742620723DCC00AED3D2 /* MEGAShowPasswordReminderRequestDelegate.h */, 8364742720723DCC00AED3D2 /* MEGAShowPasswordReminderRequestDelegate.m */, + 5B1AB4AD20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.h */, + 5B1AB4AE20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m */, + 5B5E591120EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.h */, + 5B5E591220EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m */, + 5B20B00F20E6300300511C0F /* MEGAGenericRequestDelegate.h */, + 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */, 5B14A41420D10042005F8146 /* MEGAMultiFactorAuthCheckRequestDelegate.h */, 5B14A41320D10042005F8146 /* MEGAMultiFactorAuthCheckRequestDelegate.m */, ); @@ -3268,6 +3417,7 @@ E889C9DE1B55652B00ECEFDF /* My Account */ = { isa = PBXGroup; children = ( + 77597007219EB7FD00B31671 /* Notifications */, 5B48F8001F4C73D0001E3A29 /* Achievements */, 41A66D031AE78FF9007D7E20 /* Upgrade */, E889C9E51B5565C900ECEFDF /* MyAccount.storyboard */, @@ -3295,8 +3445,6 @@ E8B519221AC9CD8000BB7324 /* TransfersViewController.m */, E8B519231AC9CD8000BB7324 /* TransferTableViewCell.h */, E8B519241AC9CD8000BB7324 /* TransferTableViewCell.m */, - 832FC4432130069000740123 /* QueuedTransferItem.h */, - 832FC4442130069100740123 /* QueuedTransferItem.m */, ); path = Transfers; sourceTree = ""; @@ -3593,6 +3741,7 @@ E8D36F791AD677C6006D4D40 /* Transfers.storyboard in Resources */, A82E5BF41F3470B5003C149C /* Spotlight_file.png in Resources */, 832E102B202B0A3900BDD30F /* NodeActionHeaderCollectionReusableView.xib in Resources */, + 777615B12175D88C00A7796D /* InputView.xib in Resources */, 4152268B1A692ECC00EC7BB6 /* Cloud.storyboard in Resources */, 83681579202AFBC000CA8EFC /* CustomActionViewController.xib in Resources */, 41EABF171DA7C5250087271C /* Chat.storyboard in Resources */, @@ -3601,6 +3750,7 @@ 5BB68CA220359B5400B03C00 /* PasswordStrengthIndicatorView.xib in Resources */, 41A72F1F1C22EA1600516603 /* LTHPasscodeViewController.bundle in Resources */, E8CD198A1A9270CB00CB8B2A /* UnavailableLinkView.xib in Resources */, + 7759700C219EBCE700B31671 /* Notifications.storyboard in Resources */, 5B5FBDDF20BD8CB2000EE35B /* TwoFactorAuthentication.storyboard in Resources */, A841E63E20CAB477005E1D1B /* MEGAMessageCallEndedView.xib in Resources */, 417279331DB663FA001AC818 /* JSQMessagesAssets.bundle in Resources */, @@ -3734,15 +3884,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 41EC68551D76E0F6003EC803 /* PieChartView.m in Sources */, 9419B6C01F20D1D700FEBE31 /* MEGAIndexer.m in Sources */, 832E1025202B09AE00BDD30F /* NodeActionCollectionViewCell.m in Sources */, 41B3B8301AD591A80093A88D /* PasscodeTableViewController.m in Sources */, 415226921A692ECC00EC7BB6 /* ContactTableViewCell.m in Sources */, 417279351DB663FA001AC818 /* NSString+JSQMessages.m in Sources */, 417279391DB663FA001AC818 /* JSQMessagesViewController.m in Sources */, + 83E076F52179D7BC00058409 /* NodeCollectionViewCell.m in Sources */, 41D21E661D01AEC3002E6296 /* CTAssetsGridViewController.m in Sources */, 41AB8EC11A7103B300E40A39 /* BrowserViewController.m in Sources */, + 5B5E591320EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m in Sources */, 417279371DB663FA001AC818 /* UIImage+JSQMessages.m in Sources */, 4172794D1DB663FA001AC818 /* JSQVideoMediaItem.m in Sources */, 77C544FF2028542D00A9ACD6 /* MEGAPhotoBrowserAnimator.m in Sources */, @@ -3754,10 +3905,13 @@ 41EAAE4B1D06B49400622FAF /* PHAssetCollection+CTAssetsPickerController.m in Sources */, 77519C36203707F600FDF92B /* MEGARenameRequestDelegate.m in Sources */, E8BD127F1DB76E03003DC53F /* MasterKeyViewController.m in Sources */, + 7759700A219EB85300B31671 /* NotificationsTableViewController.m in Sources */, 41D21E691D01AEC3002E6296 /* CTAssetsNavigationController.m in Sources */, 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */, 41D578241B84A0FB00334ED3 /* PreviewDocumentViewController.m in Sources */, + 7759700F219EC2D700B31671 /* NotificationTableViewCell.m in Sources */, 5BFA346B1FAB4169005BFC4E /* MEGARemoveRequestDelegate.m in Sources */, + 83FB1431219AC7CD00418BE6 /* OfflineTableViewViewController.m in Sources */, 7747B2D8208625B50011814E /* Reachability.m in Sources */, 417279471DB663FA001AC818 /* JSQLocationMediaItem.m in Sources */, A8665F5220ADA1600044F8F6 /* PWProgressView.m in Sources */, @@ -3780,6 +3934,7 @@ 5BC4912F20AC457F00F7F952 /* SetupTwoFactorAuthenticationTableViewController.m in Sources */, 417279361DB663FA001AC818 /* UIColor+JSQMessages.m in Sources */, 137361931A6664C300B740E8 /* AppDelegate.m in Sources */, + 83FB1434219AC7DD00418BE6 /* OfflineCollectionViewController.m in Sources */, 5BFA346E1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m in Sources */, 4172793B1DB663FA001AC818 /* JSQMessagesAvatarImageFactory.m in Sources */, 41D21E711D01AEC3002E6296 /* CTAssetThumbnailOverlay.m in Sources */, @@ -3788,11 +3943,11 @@ 4131997D1BD7BD5D002CD6EA /* NSDate+DateTools.m in Sources */, 5B17C9B31F73DE310093F162 /* InviteFriendsViewController.m in Sources */, 4172793E1DB663FA001AC818 /* JSQMessagesTimestampFormatter.m in Sources */, - 832FC4452130069100740123 /* QueuedTransferItem.m in Sources */, E8C8D73F1B1DCB4000BCDBA4 /* MEGANavigationController.m in Sources */, A8F5D67A1ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m in Sources */, 41AC66481AB1D493003FE8DA /* AboutTableViewController.m in Sources */, 41D21E5D1D01AEC3002E6296 /* CTAssetCollectionViewController.m in Sources */, + 5B6CCF0C21788A3A0049AD14 /* MEGATransferList+MNZCategory.m in Sources */, 4152269B1A692ECC00EC7BB6 /* MEGASdkManager.m in Sources */, E81616731A8397B5002EFDD7 /* FolderLinkViewController.m in Sources */, A8C262B01FA1D876006172CE /* MEGALocalImageView.m in Sources */, @@ -3802,12 +3957,14 @@ 41D21E641D01AEC3002E6296 /* CTAssetsGridView.m in Sources */, 834AD6052024694200D2527F /* MGSwipeButton.m in Sources */, 4172795D1DB663FA001AC818 /* JSQMessagesTypingIndicatorFooterView.m in Sources */, + 83FB1439219C372800418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */, 41D21E701D01AEC3002E6296 /* CTAssetsViewControllerTransition.m in Sources */, 41D408101B8F240B0001F8BE /* MEGAPurchase.m in Sources */, 4131997A1BD7BD5D002CD6EA /* DTTimePeriodChain.m in Sources */, A85740C52032F6B80033034C /* MOChatDraft+CoreDataClass.m in Sources */, 4122C0E61B274E8C001CE833 /* LocalizationSystem.m in Sources */, 5BADE39A1F02BD1F009C9AD7 /* MEGANodeList+MNZCategory.m in Sources */, + 839D5A9721C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */, 5B367576204FFBF60087362D /* SendToChatActivity.m in Sources */, 5B00B6E8210745BF0076D5BB /* FileManagementTableViewController.m in Sources */, 5B78F0B21F3C5362007ED721 /* MyAccountHallViewController.m in Sources */, @@ -3822,10 +3979,12 @@ 832E102A202B0A3900BDD30F /* NodeActionHeaderCollectionReusableView.m in Sources */, 945E1F0C1FE10CE8000C5E21 /* MEGAInputToolbar.m in Sources */, 41ADA9F21ADE7AC100641BB4 /* HelpTableViewController.m in Sources */, + A8B354D421354680002018A9 /* MEGAChatNotificationDelegate.m in Sources */, A8F5D67D1ED5B0990087DA40 /* MEGASendSignupLinkRequestDelegate.m in Sources */, 8361359021074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.m in Sources */, 4156C80C1AD7E79A00F5E818 /* LTHKeychainUtils.m in Sources */, 77F43DD92034637300AE4F42 /* UICollectionView+MNZCategory.m in Sources */, + 83369CE42179D0A0002B09C3 /* CloudDriveCollectionViewController.m in Sources */, 41EAAE461D06B49400622FAF /* NSBundle+CTAssetsPickerController.m in Sources */, A85740C42032F6B80033034C /* MOChatDraft+CoreDataProperties.m in Sources */, 5B222A74205A9CD40083D433 /* MEGAChatAttachNodeRequestDelegate.m in Sources */, @@ -3836,6 +3995,7 @@ A819B33F1EAFA7E2004592F9 /* CustomModalAlertViewController.m in Sources */, A8F6C5F91FAB4295001E274D /* MEGAChatEnableDisableVideoRequestDelegate.m in Sources */, 7754ED9920B47A2400FF8B7B /* MEGALoginToFolderLinkRequestDelegate.m in Sources */, + 830B548A2178845300BE1E1F /* CloudDriveTableViewController.m in Sources */, 4131997C1BD7BD5D002CD6EA /* DTTimePeriodGroup.m in Sources */, 830EF9A420062F3800FAA947 /* DevicePermissionsHelper.m in Sources */, 831FBEA220EF697200AC1B91 /* MEGAArchiveChatRequestDelegate.m in Sources */, @@ -3846,6 +4006,7 @@ A815FCEA1FE2AD080075AC29 /* UIApplication+MNZCategory.m in Sources */, A802B1FA1FAB293700AC8BB0 /* MEGAChatBaseRequestDelegate.m in Sources */, 415226911A692ECC00EC7BB6 /* ContactsViewController.m in Sources */, + A8F0A1C7217898F600575C57 /* MEGALocalNotificationManager.m in Sources */, E889C9E91B566EC400ECEFDF /* UsageViewController.m in Sources */, 417279511DB663FA001AC818 /* JSQMessagesCollectionViewCellIncoming.m in Sources */, 41EAAE491D06B49400622FAF /* NSNumberFormatter+CTAssetsPickerController.m in Sources */, @@ -3855,6 +4016,7 @@ 417279551DB663FA001AC818 /* JSQMessagesComposerTextView.m in Sources */, 5BB68CA120359B5400B03C00 /* PasswordStrengthIndicatorView.m in Sources */, E8B519261AC9CD8000BB7324 /* TransfersViewController.m in Sources */, + 5B20B00A20E61DDF00511C0F /* MEGALinkManager.m in Sources */, 5B64D4EE2111D5DE004CD155 /* EnabledTwoFactorAuthenticationViewController.m in Sources */, 417279441DB663FA001AC818 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */, 41D21E731D01AEC3002E6296 /* CTAssetThumbnailView.m in Sources */, @@ -3868,6 +4030,7 @@ 832F19EB2091BA970067679E /* MOMediaDestination+CoreDataProperties.m in Sources */, 5B118EC31FB1B0C6005D7D90 /* NSMutableArray+MNZCategory.m in Sources */, E8CD19871A9266D600CB8B2A /* UnavailableLinkView.m in Sources */, + 5B1AB4AF20E5407300FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m in Sources */, E8D4B8911A69429200A3B2E2 /* ConfirmAccountViewController.m in Sources */, 835223AD20568BB200A47438 /* MEGAGetFolderInfoRequestDelegate.m in Sources */, 5B5737D61EFBE6410027ECCD /* MEGANode+MNZCategory.m in Sources */, @@ -3884,6 +4047,7 @@ 41D21E5E1D01AEC3002E6296 /* CTAssetItemViewController.m in Sources */, 77D9A5712023814800B48470 /* MEGAStartDownloadTransferDelegate.m in Sources */, 5BD4A0211F838606000E24E6 /* ViewFrameAccessor.m in Sources */, + 831D61A6218738D20038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */, 417279501DB663FA001AC818 /* JSQMessagesCollectionViewCell.m in Sources */, 77B3AE0F215D0588008E889A /* InitialLaunchViewController.m in Sources */, 5B48F8031F4C73FE001E3A29 /* AchievementsViewController.m in Sources */, @@ -3891,6 +4055,7 @@ 837A88BF20B42290007B55D1 /* CloudDriveViewController.m in Sources */, A8AA4F7D1ED433CA00E7BA23 /* MEGABaseRequestDelegate.m in Sources */, 41D21DF21D01AE89002E6296 /* NSLayoutConstraint+PureLayout.m in Sources */, + A8F0A1D82178A73300575C57 /* MOMessage+CoreDataClass.m in Sources */, A810CF3020C157BC00A6C6C5 /* FLAnimatedImageView.m in Sources */, 770629A22153855600613D6E /* OnboardingViewController.swift in Sources */, E8F791801BC5754A00C58676 /* OpenInActivity.m in Sources */, @@ -3906,6 +4071,7 @@ 415C6B921CE39D6100B13070 /* SVProgressAnimatedView.m in Sources */, 77F1DBE22056B752005D5F0E /* MEGAGetAttrUserRequestDelegate.m in Sources */, 775652D7203AD2FC009E7EC8 /* ContactLinkQRViewController.m in Sources */, + 5B0CD42A2188D71100F97A2C /* UITextField+MNZCategory.m in Sources */, 5B323F231F5834E100C5F557 /* ReferralBonusesTableViewController.m in Sources */, 41EAAE471D06B49400622FAF /* NSDateFormatter+CTAssetsPickerController.m in Sources */, 4182B4EF1BF4ADD400E6FFB8 /* ContactRequestsTableViewCell.m in Sources */, @@ -3945,6 +4111,7 @@ E87C98361CC4CC1000E2B9E6 /* AdvancedTableViewController.m in Sources */, 5B28D1371F0FB9590062998C /* MEGAImagePickerController.m in Sources */, 83654A5A203C3E4100B5E426 /* NodeVersionsViewController.m in Sources */, + 83FB143A219C372800418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */, 417279491DB663FA001AC818 /* JSQMessage.m in Sources */, 417279421DB663FA001AC818 /* JSQMessagesBubblesSizeCalculator.m in Sources */, 4172794F1DB663FA001AC818 /* JSQMessagesCollectionView.m in Sources */, @@ -3974,6 +4141,7 @@ 837A88C520B45C11007B55D1 /* SendToViewController.m in Sources */, 417279481DB663FA001AC818 /* JSQMediaItem.m in Sources */, 41A66D071AE7900F007D7E20 /* UpgradeTableViewController.m in Sources */, + 5B20B01120E6300300511C0F /* MEGAGenericRequestDelegate.m in Sources */, 4172794E1DB663FA001AC818 /* JSQMessagesCellTextView.m in Sources */, 4180727A1AA5BBA00078D950 /* MEGAReachabilityManager.m in Sources */, 41D21E611D01AEC3002E6296 /* CTAssetSelectionButton.m in Sources */, @@ -3998,6 +4166,7 @@ 946BD1331FEBBE4E00B76785 /* MEGAToolbarSelectedAssets.m in Sources */, A841E64120CE6681005E1D1B /* MEGACallEndedMediaItem.m in Sources */, 41AC33A81A926C27005118AF /* HeaderCollectionReusableView.m in Sources */, + A8F0A1D92178A73300575C57 /* MOMessage+CoreDataProperties.m in Sources */, E8B519271AC9CD8000BB7324 /* TransferTableViewCell.m in Sources */, 4172793C1DB663FA001AC818 /* JSQMessagesBubbleImageFactory.m in Sources */, 9437B93F1F55674F0058170D /* MEGAPasswordLinkRequestDelegate.m in Sources */, @@ -4010,9 +4179,11 @@ 41D21E5F1D01AEC3002E6296 /* CTAssetPlayButton.m in Sources */, A89629781FACEAF800F02F7A /* MEGAProviderDelegate.m in Sources */, 5B14A41520D10043005F8146 /* MEGAMultiFactorAuthCheckRequestDelegate.m in Sources */, + 77597006219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.m in Sources */, 83681578202AFBC000CA8EFC /* CustomActionViewController.m in Sources */, 4172794B1DB663FA001AC818 /* JSQMessagesBubbleImage.m in Sources */, 41CD14701DB150F600E545BC /* MessagesViewController.m in Sources */, + 777615AF2175D87D00A7796D /* InputView.m in Sources */, A8F6C5F31FAB3FD3001E274D /* MEGAChatEnableDisableAudioRequestDelegate.m in Sources */, 413199771BD7BD5D002CD6EA /* DTConstants.m in Sources */, 832E1032202D8E6900BDD30F /* NodePropertyTableViewCell.m in Sources */, @@ -4022,6 +4193,7 @@ 4152268F1A692ECC00EC7BB6 /* NodeTableViewCell.m in Sources */, E8D5BF691BF35DC400955550 /* SharedItemsViewController.m in Sources */, 835223B1205957DD00A47438 /* ItemCollectionViewCell.m in Sources */, + 831D61A5218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */, 94624CFB1F50484200D52504 /* MEGAExportRequestDelegate.m in Sources */, 77F1DBDF2056AA13005D5F0E /* MEGAContactLinkQueryRequestDelegate.m in Sources */, 417279451DB663FA001AC818 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */, @@ -4059,6 +4231,7 @@ 944B205D1EE55533003C967B /* UIAlertAction+MNZCategory.m in Sources */, 415DCF241BF48EFA00914A1E /* ContactRequestsViewController.m in Sources */, 4172794C1DB663FA001AC818 /* JSQPhotoMediaItem.m in Sources */, + A8F43939218726690054472A /* PieChartView.m in Sources */, 835223B42059588300A47438 /* ItemListViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4069,11 +4242,13 @@ files = ( 834AD6072024697200D2527F /* MGSwipeButton.m in Sources */, 77264F242147D60600DC6BCB /* MEGACopyRequestDelegate.m in Sources */, + 83FB143D219C3C7100418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */, 83B841682051913C00E5AC69 /* MEGAGetThumbnailRequestDelegate.m in Sources */, 5BFA34701FAC5857005BFC4E /* MEGAMoveRequestDelegate.m in Sources */, 9432294B1EDD9CE5007DB542 /* MEGAUser+MNZCategory.m in Sources */, A824A91D2039F50C00D777D2 /* SDAVAssetExportSession.m in Sources */, 832F19ED2091BD8E0067679E /* MOMediaDestination+CoreDataProperties.m in Sources */, + 831D61A9218738F30038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */, 9456054B1EDD7AD70094EF0F /* LTHPasscodeViewController.m in Sources */, 944B205E1EE5C6C2003C967B /* LaunchViewController.m in Sources */, 5B71631D203D9CE1001E5FBC /* NodeTableViewCell.m in Sources */, @@ -4092,6 +4267,7 @@ 5BA81EA41FC30C9A00C8146C /* MEGAShareRequestDelegate.m in Sources */, 5BEACE2B1FB4740E0010E02F /* UIAlertAction+MNZCategory.m in Sources */, 943229501EDD9CF8007DB542 /* UIImage+MNZCategory.m in Sources */, + 831D61A7218738EB0038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */, 9456053D1EDD54F10094EF0F /* SAMKeychain.m in Sources */, A85740C62032F6E70033034C /* MOChatDraft+CoreDataClass.m in Sources */, A85740C72032F6E70033034C /* MOChatDraft+CoreDataProperties.m in Sources */, @@ -4102,9 +4278,11 @@ 943229261EDD8495007DB542 /* MEGAAssetOperation.m in Sources */, 945605381EDD52C00094EF0F /* SVProgressAnimatedView.m in Sources */, 5BEACE201FB472CC0010E02F /* MEGANodeList+MNZCategory.m in Sources */, + 83FB143B219C3C6D00418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */, 945605531EDD7E0C0094EF0F /* CameraUploads.m in Sources */, 945605471EDD67520094EF0F /* MEGASdkManager.m in Sources */, 9432292C1EDD8542007DB542 /* CameraUploadsTableViewController.m in Sources */, + 839D5A9821C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */, 945605521EDD7E020094EF0F /* GetLinkActivity.m in Sources */, 77B3AE212163BD2B008E889A /* DevicePermissionsHelper.m in Sources */, 945605441EDD66CE0094EF0F /* UIColor+MNZCategory.m in Sources */, @@ -4117,6 +4295,7 @@ 9456054E1EDD7DBD0094EF0F /* OpenInActivity.m in Sources */, 945605391EDD52C00094EF0F /* SVIndefiniteAnimatedView.m in Sources */, 943229371EDD88B1007DB542 /* SaveToCameraRollActivity.m in Sources */, + 8310488B21AECDBA0057400D /* DTConstants.m in Sources */, 832F19EF2091BD980067679E /* MOMediaDestination+CoreDataClass.m in Sources */, 834AD6092024697600D2527F /* MGSwipeTableCell.m in Sources */, 774089692164AB1300B93D3E /* CustomModalAlertViewController.m in Sources */, @@ -4129,6 +4308,7 @@ 945605421EDD65960094EF0F /* LocalizationSystem.m in Sources */, 5B2E78CE1FA0C3450096C9BE /* MEGACreateFolderRequestDelegate.m in Sources */, 94624CFD1F50517700D52504 /* MEGAExportRequestDelegate.m in Sources */, + A8F0A1DA2178B41900575C57 /* MOMessage+CoreDataClass.m in Sources */, 9432294C1EDD9CE9007DB542 /* NSFileManager+MNZCategory.m in Sources */, 8361359121074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.m in Sources */, ); @@ -4153,6 +4333,7 @@ files = ( 77892E9E20E501DC0020A533 /* ContactTableViewCell.m in Sources */, 94892C921EFBEA3D00AEAC25 /* MEGASdkManager.m in Sources */, + 8310488A21AECD860057400D /* DTConstants.m in Sources */, 77F95CF320E3E60A00F42D9C /* MEGAChatAttachNodeRequestDelegate.m in Sources */, 94624CFE1F50518400D52504 /* MEGABaseRequestDelegate.m in Sources */, 943F8B201EF2705300AFD89F /* ShareViewController.m in Sources */, @@ -4164,6 +4345,7 @@ 77892E9F20E5029B0020A533 /* MOUser+CoreDataProperties.m in Sources */, 5BEACE2A1FB474060010E02F /* MEGAUser+MNZCategory.m in Sources */, 94892C901EFBEA1F00AEAC25 /* SAMKeychain.m in Sources */, + 839D5A9921C25A7D00FAE757 /* CloudDriveGridFlowLayout.m in Sources */, 775D9C0B20EA0D49001BF1E8 /* ShareAttachment.m in Sources */, 941135561F01004000D33428 /* ShareFolderActivity.m in Sources */, 5BEACE241FB473940010E02F /* UIImageView+MNZCategory.m in Sources */, @@ -4175,10 +4357,14 @@ A824A91C2039F50B00D777D2 /* SDAVAssetExportSession.m in Sources */, 774089682164AAF000B93D3E /* CustomModalAlertViewController.m in Sources */, 7747B2DB208625B70011814E /* Reachability.m in Sources */, + 831D61A8218738EC0038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */, 8361359221074C5D002FA3CC /* MOUploadTransfer+CoreDataClass.m in Sources */, A81F2C6A1F334FB2008600A5 /* MEGAProcessAsset.m in Sources */, + A8F0A1DC2178B43300575C57 /* MOMessage+CoreDataClass.m in Sources */, 943F8B281EF283D300AFD89F /* UIDevice+MNZCategory.m in Sources */, A836581B21020FB9005E616F /* ItemListViewController.m in Sources */, + 83FB143E219C3C7100418BE6 /* MOOfflineFolderLayout+CoreDataClass.m in Sources */, + 83FB143C219C3C6E00418BE6 /* MOOfflineFolderLayout+CoreDataProperties.m in Sources */, 77F95CEF20E3B14D00F42D9C /* ShareFilesDestinationTableViewController.m in Sources */, 5BEACE251FB473A20010E02F /* UIImage+MNZCategory.m in Sources */, 94892C931EFBEA4100AEAC25 /* MEGALogger.m in Sources */, @@ -4225,6 +4411,7 @@ 832F19EE2091BD8E0067679E /* MOMediaDestination+CoreDataProperties.m in Sources */, 83B841672051905E00E5AC69 /* MEGAGetThumbnailRequestDelegate.m in Sources */, 834AD60A2024697700D2527F /* MGSwipeTableCell.m in Sources */, + 831D61AA218738F40038F5B8 /* MOFolderLayout+CoreDataProperties.m in Sources */, A85740C82032F6E80033034C /* MOChatDraft+CoreDataClass.m in Sources */, 77F95CF020E3E5C000F42D9C /* SendToViewController.m in Sources */, 832F19F02091BD980067679E /* MOMediaDestination+CoreDataClass.m in Sources */, diff --git a/iMEGA/API/Chat/MEGAChatNotificationDelegate.h b/iMEGA/API/Chat/MEGAChatNotificationDelegate.h new file mode 100644 index 0000000000..6d59b71a26 --- /dev/null +++ b/iMEGA/API/Chat/MEGAChatNotificationDelegate.h @@ -0,0 +1,7 @@ + +#import +#import "MEGAChatSdk.h" + +@interface MEGAChatNotificationDelegate : NSObject + +@end diff --git a/iMEGA/API/Chat/MEGAChatNotificationDelegate.m b/iMEGA/API/Chat/MEGAChatNotificationDelegate.m new file mode 100644 index 0000000000..f8ce2f3205 --- /dev/null +++ b/iMEGA/API/Chat/MEGAChatNotificationDelegate.m @@ -0,0 +1,41 @@ + +#import "MEGAChatNotificationDelegate.h" + +#import "MEGALocalNotificationManager.h" +#import "MEGAStore.h" +#import "MessagesViewController.h" +#import "UIApplication+MNZCategory.h" + +@implementation MEGAChatNotificationDelegate + +#pragma mark - MEGAChatNotificationDelegate + +- (void)onChatNotification:(MEGAChatSdk *)api chatId:(uint64_t)chatId message:(MEGAChatMessage *)message { + MEGALogDebug(@"On chat %@ notification message %@", [MEGASdk base64HandleForUserHandle:chatId], message); + + [UIApplication sharedApplication].applicationIconBadgeNumber = api.unreadChats; + + if (@available(iOS 10.0, *)) { + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + if ([UIApplication.mnz_visibleViewController isKindOfClass:[MessagesViewController class]] && message.status != MEGAChatMessageStatusSeen) { + MessagesViewController *messagesVC = (MessagesViewController *) UIApplication.mnz_visibleViewController; + if (messagesVC.chatRoom.chatId == chatId) { + MEGALogDebug(@"The chat room %@ is opened, ignore notification", [MEGASdk base64HandleForHandle:chatId]); + return; + } + } + MEGAChatRoom *chatRoom = [api chatRoomForChatId:chatId]; + if (chatRoom && message) { + MEGALocalNotificationManager *localNotificationManager = [[MEGALocalNotificationManager alloc] initWithChatRoom:chatRoom message:message silent:YES]; + [localNotificationManager proccessNotification]; + } + } else { + MOMessage *mMessage = [[MEGAStore shareInstance] fetchMessageWithChatId:chatId messageId:message.messageId]; + MEGAChatRoom *chatRoom = [api chatRoomForChatId:chatId]; + MEGALocalNotificationManager *localNotificationManager = [[MEGALocalNotificationManager alloc] initWithChatRoom:chatRoom message:message silent:mMessage ? NO : YES]; + [localNotificationManager proccessNotification]; + } + } +} + +@end diff --git a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m index f61664fba6..4ae377f567 100644 --- a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m +++ b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m @@ -56,10 +56,10 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEArgs: { NSString *message = AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used"); - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; break; } diff --git a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m index 06349751e0..ea5c4d0e1c 100755 --- a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m +++ b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m @@ -37,7 +37,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (error.type == MEGAErrorTypeApiEAccess) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"permissionTitle", @"Error title shown when you are trying to do an action with a file or folder and you don't have the necessary permissions") message:AMLocalizedString(@"permissionMessage", @"Error message shown when you are trying to do an action with a file or folder and you don't have the necessary permissions") preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } else { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, error.name]]; diff --git a/iMEGA/API/Requests/MEGAGenericRequestDelegate.h b/iMEGA/API/Requests/MEGAGenericRequestDelegate.h new file mode 100644 index 0000000000..f0620d0d52 --- /dev/null +++ b/iMEGA/API/Requests/MEGAGenericRequestDelegate.h @@ -0,0 +1,10 @@ + +#import "MEGABaseRequestDelegate.h" + +@interface MEGAGenericRequestDelegate : MEGABaseRequestDelegate + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithRequestCompletion:(void (^)(MEGARequest *request))requestCompletion errorCompletion:(void (^)(MEGARequest *request, MEGAError *error))errorCompletion; + +@end diff --git a/iMEGA/API/Requests/MEGAGenericRequestDelegate.m b/iMEGA/API/Requests/MEGAGenericRequestDelegate.m new file mode 100644 index 0000000000..f1d5118d57 --- /dev/null +++ b/iMEGA/API/Requests/MEGAGenericRequestDelegate.m @@ -0,0 +1,43 @@ + +#import "MEGAGenericRequestDelegate.h" + +@interface MEGAGenericRequestDelegate () + +@property (nonatomic, copy) void (^errorCompletion)(MEGARequest *request, MEGAError *error); +@property (nonatomic, copy) void (^requestCompletion)(MEGARequest *request); + +@end + +@implementation MEGAGenericRequestDelegate + +- (instancetype)initWithRequestCompletion:(void (^)(MEGARequest *request))requestCompletion errorCompletion:(void (^)(MEGARequest *request, MEGAError *error))errorCompletion { + self = [super init]; + if (self) { + _requestCompletion = requestCompletion; + _errorCompletion = errorCompletion; + } + + return self; +} + +#pragma mark - MEGARequestDelegate + +- (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { + [super onRequestStart:api request:request]; +} + +- (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { + [super onRequestFinish:api request:request error:error]; + + if (error.type) { + if (self.errorCompletion) { + self.errorCompletion(request, error); + } + } else { + if (self.requestCompletion) { + self.requestCompletion(request); + } + } +} + +@end diff --git a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h index e05d943772..b80aca7c17 100644 --- a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h @@ -3,6 +3,8 @@ @interface MEGAGetPublicNodeRequestDelegate : MEGABaseRequestDelegate +@property (nonatomic) BOOL savePublicHandle; + - (id)init NS_UNAVAILABLE; - (instancetype)initWithCompletion:(void (^)(MEGARequest *request, MEGAError *error))completion; diff --git a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m index fdc922cb8d..5392964eb5 100644 --- a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m @@ -26,6 +26,11 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { [super onRequestFinish:api request:request error:error]; + if (error.type == MEGAErrorTypeApiOk && self.savePublicHandle && request.publicNode) { + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedLongLong:request.publicNode.handle] forKey:@"kLastPublicHandleAccessed"]; + [[NSUserDefaults standardUserDefaults] setDouble:[NSDate date].timeIntervalSince1970 forKey:@"kLastPublicTimestampAccessed"]; + } + if (self.completion) { self.completion(request, error); } diff --git a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m index 5c94d34d87..644714e36d 100755 --- a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m @@ -106,7 +106,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (self.viewController) { [self.viewController presentViewController:customModalAlertVC animated:YES completion:nil]; } else { - [UIApplication.mnz_visibleViewController presentViewController:customModalAlertVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; } } } diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.m b/iMEGA/API/Requests/MEGALoginRequestDelegate.m index 8a49ba75d0..b7015bb5fc 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.m @@ -84,10 +84,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEExpired: { if (self.errorCompletion) { self.errorCompletion(error); + return; } else { message = [NSString stringWithFormat:@"%@ %@", request.requestString, error.name]; + break; } - return; } case MEGAErrorTypeApiEFailed: @@ -118,7 +119,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; return; } diff --git a/iMEGA/API/Requests/MEGAMoveRequestDelegate.m b/iMEGA/API/Requests/MEGAMoveRequestDelegate.m index 998c4a0c2a..a14fc902fa 100755 --- a/iMEGA/API/Requests/MEGAMoveRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAMoveRequestDelegate.m @@ -95,8 +95,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG message = [NSString stringWithFormat:AMLocalizedString(@"filesFolderMovedToRubbishBinMessage", @"Success message shown when you have moved {1+} files and 1 folder to the rubbish bin"), self.numberOfFiles]; } else { message = AMLocalizedString(@"filesFoldersMovedToRubbishBinMessage", @"Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders to the rubbish bin"); - NSString *filesString = [NSString stringWithFormat:@"%lu", self.numberOfFiles]; - NSString *foldersString = [NSString stringWithFormat:@"%lu", self.numberOfFolders]; + NSString *filesString = [NSString stringWithFormat:@"%tu", self.numberOfFiles]; + NSString *foldersString = [NSString stringWithFormat:@"%tu", self.numberOfFolders]; message = [message stringByReplacingOccurrencesOfString:@"[A]" withString:filesString]; message = [message stringByReplacingOccurrencesOfString:@"[B]" withString:foldersString]; } @@ -129,8 +129,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG message = [NSString stringWithFormat:AMLocalizedString(@"moveFilesFolderMessage", @"Success message shown when you have moved {1+} files and 1 folder"), self.numberOfFiles]; } else { message = AMLocalizedString(@"moveFilesFoldersMessage", @"Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders"); - NSString *filesString = [NSString stringWithFormat:@"%lu", self.numberOfFiles]; - NSString *foldersString = [NSString stringWithFormat:@"%lu", self.numberOfFolders]; + NSString *filesString = [NSString stringWithFormat:@"%tu", self.numberOfFiles]; + NSString *foldersString = [NSString stringWithFormat:@"%tu", self.numberOfFolders]; message = [message stringByReplacingOccurrencesOfString:@"[A]" withString:filesString]; message = [message stringByReplacingOccurrencesOfString:@"[B]" withString:foldersString]; } diff --git a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h new file mode 100644 index 0000000000..180a858fd3 --- /dev/null +++ b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h @@ -0,0 +1,12 @@ + +#import "MEGABaseRequestDelegate.h" + +#import "URLType.h" + +@interface MEGAQueryRecoveryLinkRequestDelegate : MEGABaseRequestDelegate + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithRequestCompletion:(void (^)(MEGARequest *request, MEGAError *error))requestCompletion urlType:(URLType)urlType; + +@end diff --git a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m new file mode 100644 index 0000000000..dd6d4d3963 --- /dev/null +++ b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m @@ -0,0 +1,157 @@ + +#import "MEGAQueryRecoveryLinkRequestDelegate.h" + +#import "SAMKeychain.h" +#import "SVProgressHUD.h" + +#import "ChangePasswordViewController.h" +#import "MEGASdkManager.h" +#import "MEGANavigationController.h" +#import "MEGALinkManager.h" +#import "NSString+MNZCategory.h" +#import "LoginViewController.h" +#import "TwoFactorAuthenticationViewController.h" +#import "UIApplication+MNZCategory.h" +#import "UITextField+MNZCategory.h" + +@interface MEGAQueryRecoveryLinkRequestDelegate () + +@property (nonatomic, copy) void (^completion)(MEGARequest *request, MEGAError *error); +@property (nonatomic) URLType urlType; + +@property (nonatomic) NSString *link; +@property (nonatomic) NSString *email; + +@end + +@implementation MEGAQueryRecoveryLinkRequestDelegate + +- (instancetype)initWithRequestCompletion:(void (^)(MEGARequest *request, MEGAError *error))requestCompletion urlType:(URLType)urlType { + self = [super init]; + if (self) { + _completion = requestCompletion; + _urlType = urlType; + } + return self; +} + +#pragma mark - Private + +- (void)presentChangeViewType:(ChangeType)changeType email:(NSString *)email masterKey:(NSString *)masterKey link:(NSString *)link { + ChangePasswordViewController *changePasswordVC = [[UIStoryboard storyboardWithName:@"Settings" bundle:nil] instantiateViewControllerWithIdentifier:@"ChangePasswordViewControllerID"]; + changePasswordVC.changeType = changeType; + changePasswordVC.email = email; + changePasswordVC.masterKey = masterKey; + changePasswordVC.link = link; + + MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:changePasswordVC]; + [navigationController addCancelButton]; + + UIViewController *visibleViewController = UIApplication.mnz_presentingViewController; + if ([visibleViewController isKindOfClass:UIAlertController.class]) { + [visibleViewController dismissViewControllerAnimated:NO completion:^{ + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; + }]; + } else { + [visibleViewController presentViewController:navigationController animated:YES completion:nil]; + } +} + +- (void)alertTextFieldDidChange:(UITextField *)textField { + UIAlertController *alertController = (UIAlertController *)UIApplication.mnz_visibleViewController; + if (alertController) { + UIAlertAction *rightButtonAction = alertController.actions.lastObject; + rightButtonAction.enabled = !textField.text.mnz_isEmpty; + } +} + +#pragma mark - MEGARequestDelegate + +- (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { + [super onRequestStart:api request:request]; +} + +- (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { + [super onRequestFinish:api request:request error:error]; + + if (error.type) { + switch (error.type) { + case MEGAErrorTypeApiEExpired: { + NSString *alertTitle; + if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { + alertTitle = AMLocalizedString(@"cancellationLinkHasExpired", @"During account cancellation (deletion)"); + } else if (MEGALinkManager.urlType == URLTypeRecoverLink) { + alertTitle = AMLocalizedString(@"recoveryLinkHasExpired", @"Message shown during forgot your password process if the link to reset password has expired"); + } + UIAlertController *linkHasExpiredAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:nil preferredStyle:UIAlertControllerStyleAlert]; + [linkHasExpiredAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + + [UIApplication.mnz_presentingViewController presentViewController:linkHasExpiredAlertController animated:YES completion:nil]; + break; + } + + case MEGAErrorTypeApiENoent: { + [MEGALinkManager showLinkNotValid]; + break; + } + + default: { + [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, error.name]]; + break; + } + } + } else { + if (MEGALinkManager.urlType == URLTypeChangeEmailLink) { + [MEGALinkManager presentConfirmViewWithURLType:URLTypeChangeEmailLink link:request.link email:request.email]; + } else if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { + [MEGALinkManager presentConfirmViewWithURLType:URLTypeCancelAccountLink link:request.link email:request.email]; + } else if (MEGALinkManager.urlType == URLTypeRecoverLink) { + if ([UIApplication.sharedApplication.keyWindow.rootViewController isKindOfClass:MEGANavigationController.class]) { + MEGANavigationController *navigationController = (MEGANavigationController *)UIApplication.sharedApplication.keyWindow.rootViewController; + if ([navigationController.topViewController isKindOfClass:TwoFactorAuthenticationViewController.class]) { + [navigationController popViewControllerAnimated:NO]; + + if ([navigationController.topViewController isKindOfClass:LoginViewController.class]) { + LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; + [loginVC cleanPasswordTextField]; + } + } + } + + if (request.flag) { + UIAlertController *masterKeyLoggedInAlertController; + if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { + masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:AMLocalizedString(@"youRecoveryKeyIsGoingTo", @"Text of the alert after opening the recovery link to reset pass being logged.") preferredStyle:UIAlertControllerStyleAlert]; + } else { + masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:AMLocalizedString(@"pleaseEnterYourRecoveryKey", @"A message shown to explain that the user has to input (type or paste) their recovery key to continue with the reset password process.") preferredStyle:UIAlertControllerStyleAlert]; + [masterKeyLoggedInAlertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { + textField.placeholder = AMLocalizedString(@"recoveryKey", @"Label for any 'Recovery Key' button, link, text, title, etc. Preserve uppercase - (String as short as possible). The Recovery Key is the new name for the account 'Master Key', and can unlock (recover) the account if the user forgets their password."); + [textField addTarget:self action:@selector(alertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + return !textField.text.mnz_isEmpty; + }; + }]; + } + + UIAlertAction *okAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + NSString *masterKey = masterKeyLoggedInAlertController.textFields.count ? masterKeyLoggedInAlertController.textFields[0].text : [[MEGASdkManager sharedMEGASdk] masterKey]; + [self presentChangeViewType:ChangeTypeResetPassword email:MEGALinkManager.emailOfNewSignUpLink masterKey:masterKey link:request.link]; + MEGALinkManager.emailOfNewSignUpLink = nil; + }]; + okAlertAction.enabled = !masterKeyLoggedInAlertController.textFields.count; + [masterKeyLoggedInAlertController addAction:okAlertAction]; + + [masterKeyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + + MEGALinkManager.emailOfNewSignUpLink = request.email; + + [UIApplication.mnz_presentingViewController presentViewController:masterKeyLoggedInAlertController animated:YES completion:nil]; + } else { + [self presentChangeViewType:ChangeTypeParkAccount email:request.email masterKey:nil link:request.link]; + } + } + } +} + +@end diff --git a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h new file mode 100644 index 0000000000..6a8e45e69d --- /dev/null +++ b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h @@ -0,0 +1,12 @@ + +#import "MEGABaseRequestDelegate.h" + +#import "URLType.h" + +@interface MEGAQuerySignupLinkRequestDelegate : MEGABaseRequestDelegate + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithCompletion:(void (^)(MEGARequest *request, MEGAError *error))requestCompletion urlType:(URLType)urlType; + +@end diff --git a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m new file mode 100644 index 0000000000..ea579486e0 --- /dev/null +++ b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m @@ -0,0 +1,133 @@ + +#import "MEGAQuerySignupLinkRequestDelegate.h" + +#import "SVProgressHUD.h" + +#import "CreateAccountViewController.h" +#import "LoginViewController.h" +#import "MEGANavigationController.h" +#import "MEGAGenericRequestDelegate.h" +#import "MEGALinkManager.h" +#import "MEGALoginRequestDelegate.h" +#import "MEGASdkManager.h" +#import "UIApplication+MNZCategory.h" + +#import "SAMKeychain.h" + +@interface MEGAQuerySignupLinkRequestDelegate () + +@property (nonatomic, copy) void (^completion)(MEGARequest *request, MEGAError *error); +@property (nonatomic) URLType urlType; + +@property (nonatomic) NSString *email; + +@end + +@implementation MEGAQuerySignupLinkRequestDelegate + +- (instancetype)initWithCompletion:(void (^)(MEGARequest *request, MEGAError *error))requestCompletion urlType:(URLType)urlType { + self = [super init]; + if (self) { + _completion = requestCompletion; + _urlType = urlType; + } + + return self; +} + +#pragma mark - Private + +- (void)manageQuerySignupLinkRequest:(MEGARequest *)request { + if (self.urlType == URLTypeConfirmationLink) { + if (request.flag) { + if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionId"]) { + MEGALoginRequestDelegate *loginRequestDelegate = [[MEGALoginRequestDelegate alloc] init]; + loginRequestDelegate.confirmAccountInOtherClient = YES; + NSString *base64pwkey = [SAMKeychain passwordForService:@"MEGA" account:@"base64pwkey"]; + NSString *stringHash = [[MEGASdkManager sharedMEGASdk] hashForBase64pwkey:base64pwkey email:request.email]; + [[MEGASdkManager sharedMEGASdk] fastLoginWithEmail:request.email stringHash:stringHash base64pwKey:base64pwkey delegate:loginRequestDelegate]; + } else { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + MEGANavigationController *navigationController = (MEGANavigationController *)rootViewController; + if ([navigationController.topViewController isKindOfClass:LoginViewController.class]) { + LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; + loginVC.emailString = request.email; + [loginVC viewWillAppear:NO]; + } + }]]; + + [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + + [MEGALinkManager resetLinkAndURLType]; + } + } else { + [MEGALinkManager presentConfirmViewWithURLType:URLTypeConfirmationLink link:MEGALinkManager.linkURL.absoluteString email:self.email]; + } + } else if (self.urlType == URLTypeNewSignUpLink && MEGALinkManager.emailOfNewSignUpLink) { + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[MEGANavigationController class]]) { + MEGANavigationController *navigationController = (MEGANavigationController *)rootViewController; + if ([navigationController.topViewController isKindOfClass:[LoginViewController class]]) { + LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; + [loginVC performSegueWithIdentifier:@"CreateAccountStoryboardSegueID" sender:MEGALinkManager.emailOfNewSignUpLink]; + } else if ([navigationController.topViewController isKindOfClass:[CreateAccountViewController class]]) { + CreateAccountViewController *createAccountVC = (CreateAccountViewController *)navigationController.topViewController; + createAccountVC.emailString = MEGALinkManager.emailOfNewSignUpLink; + [createAccountVC viewDidLoad]; + } + + MEGALinkManager.emailOfNewSignUpLink = nil; + } + + [MEGALinkManager resetLinkAndURLType]; + } + + self.email = nil; +} + +#pragma mark - MEGARequestDelegate + +- (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { + [super onRequestStart:api request:request]; +} + +- (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { + [super onRequestFinish:api request:request error:error]; + + if (error.type) { + switch (error.type) { + case MEGAErrorTypeApiEArgs: + case MEGAErrorTypeApiEIncomplete: + [MEGALinkManager showLinkNotValid]; + break; + + + case MEGAErrorTypeApiENoent: { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; + break; + } + + default: { + [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, error.name]]; + break; + } + } + + [MEGALinkManager resetLinkAndURLType]; + } else { + self.email = request.email; + MEGALinkManager.linkURL = [NSURL URLWithString:request.link]; + MEGALinkManager.emailOfNewSignUpLink = request.email; + + [self manageQuerySignupLinkRequest:request]; + } +} + +@end diff --git a/iMEGA/API/Requests/MEGARemoveRequestDelegate.m b/iMEGA/API/Requests/MEGARemoveRequestDelegate.m index e886948d4c..3aa8d1d947 100755 --- a/iMEGA/API/Requests/MEGARemoveRequestDelegate.m +++ b/iMEGA/API/Requests/MEGARemoveRequestDelegate.m @@ -78,8 +78,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG message = [NSString stringWithFormat:AMLocalizedString(@"filesFolderRemovedToRubbishBinMessage", @"Success message shown when {1+} files and 1 folder have been removed from MEGA"), self.numberOfFiles]; } else { message = AMLocalizedString(@"filesFoldersRemovedToRubbishBinMessage", @"Success message shown when [A] = {1+} files and [B] = {1+} folders have been removed from MEGA"); - NSString *filesString = [NSString stringWithFormat:@"%lu", self.numberOfFiles]; - NSString *foldersString = [NSString stringWithFormat:@"%lu", self.numberOfFolders]; + NSString *filesString = [NSString stringWithFormat:@"%tu", self.numberOfFiles]; + NSString *foldersString = [NSString stringWithFormat:@"%tu", self.numberOfFolders]; message = [message stringByReplacingOccurrencesOfString:@"[A]" withString:filesString]; message = [message stringByReplacingOccurrencesOfString:@"[B]" withString:foldersString]; } diff --git a/iMEGA/API/Requests/MEGASendSignupLinkRequestDelegate.m b/iMEGA/API/Requests/MEGASendSignupLinkRequestDelegate.m index 2212a4b5d5..b67d13dc1f 100644 --- a/iMEGA/API/Requests/MEGASendSignupLinkRequestDelegate.m +++ b/iMEGA/API/Requests/MEGASendSignupLinkRequestDelegate.m @@ -4,6 +4,8 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" +#import "UIApplication+MNZCategory.h" + @implementation MEGASendSignupLinkRequestDelegate #pragma mark - MEGARequestDelegate @@ -16,25 +18,29 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [super onRequestFinish:api request:request error:error]; if (error.type) { + NSString *title; NSString *message; switch (error.type) { case MEGAErrorTypeApiEExist: + title = @""; message = AMLocalizedString(@"emailAlreadyRegistered", @"Error text shown when the users tries to create an account with an email already in use"); break; case MEGAErrorTypeApiEArgs: + title = @""; message = AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used"); break; default: + title = AMLocalizedString(@"error", nil); message = [NSString stringWithFormat:@"%@ %@", request.requestString, error.name]; break; } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [[UIApplication sharedApplication].delegate.window.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; return; } else { diff --git a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m index c01670aea8..d0d56daedc 100644 --- a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m @@ -40,7 +40,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG PasswordReminderViewController *passwordReminderViewController = [[UIStoryboard storyboardWithName:@"PasswordReminder" bundle:nil] instantiateViewControllerWithIdentifier:@"PasswordReminderViewControllerID"]; passwordReminderViewController.logout = self.isLoggingOut; - [UIApplication.mnz_visibleViewController presentViewController:passwordReminderViewController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:passwordReminderViewController animated:YES completion:nil]; } else { if (self.isLoggingOut) { [Helper logoutAfterPasswordReminder]; diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index cf9a84be6d..e07b571f54 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -1,9 +1,7 @@ #import "AppDelegate.h" -#import #import #import -#import #import #import #import @@ -20,6 +18,7 @@ #import "DevicePermissionsHelper.h" #import "MEGAApplication.h" #import "MEGAIndexer.h" +#import "MEGALinkManager.h" #import "MEGALogger.h" #import "MEGANavigationController.h" #import "MEGANode+MNZCategory.h" @@ -33,43 +32,27 @@ #import "NSFileManager+MNZCategory.h" #import "NSString+MNZCategory.h" #import "NSURL+MNZCategory.h" -#import "UIImage+MNZCategory.h" -#import "UIImage+GKContact.h" #import "UIApplication+MNZCategory.h" -#import "BrowserViewController.h" #import "CallViewController.h" -#import "CameraUploadsPopUpViewController.h" -#import "ChangePasswordViewController.h" #import "ChatRoomsViewController.h" #import "CheckEmailAndFollowTheLinkViewController.h" #import "CloudDriveViewController.h" -#import "ConfirmAccountViewController.h" -#import "ContactRequestsViewController.h" #import "ContactsViewController.h" -#import "CreateAccountViewController.h" #import "CustomModalAlertViewController.h" -#import "DisplayMode.h" #import "InitialLaunchViewController.h" #import "LaunchViewController.h" -#import "LoginViewController.h" #import "MainTabBarController.h" -#import "MasterKeyViewController.h" #import "MEGAAssetsPickerController.h" -#import "MEGAPhotoBrowserViewController.h" -#import "MessagesViewController.h" -#import "MyAccountHallViewController.h" -#import "SettingsTableViewController.h" -#import "SharedItemsViewController.h" -#import "UnavailableLinkView.h" #import "UpgradeTableViewController.h" #import "MEGAChatCreateChatGroupRequestDelegate.h" +#import "MEGAChatNotificationDelegate.h" +#import "MEGALocalNotificationManager.h" #import "MEGACreateAccountRequestDelegate.h" #import "MEGAGetAttrUserRequestDelegate.h" #import "MEGAInviteContactRequestDelegate.h" #import "MEGALoginRequestDelegate.h" -#import "MEGAPasswordLinkRequestDelegate.h" #import "MEGAShowPasswordReminderRequestDelegate.h" #define kUserAgent @"MEGAiOS" @@ -89,9 +72,6 @@ @interface AppDelegate () *)options { MEGALogDebug(@"Application open URL %@", url); - self.link = url; + MEGALinkManager.linkURL = url; [self manageLink:url]; return YES; @@ -539,9 +516,9 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct if ([MEGAReachabilityManager isReachable]) { if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) { - self.nodeToPresentBase64Handle = userActivity.userInfo[@"kCSSearchableItemActivityIdentifier"]; + MEGALinkManager.nodeToPresentBase64Handle = userActivity.userInfo[@"kCSSearchableItemActivityIdentifier"]; if ([self.window.rootViewController isKindOfClass:[MainTabBarController class]] && ![LTHPasscodeViewController doesPasscodeExist]) { - [self presentNode]; + [MEGALinkManager presentNode]; } } else if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"] || [userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) { INInteraction *interaction = userActivity.interaction; @@ -567,10 +544,13 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct MEGAChatCall *call = [[MEGASdkManager sharedMEGAChatSdk] chatCallForChatId:self.chatRoom.chatId]; if (call.status == MEGAChatCallStatusInProgress) { MEGALogDebug(@"There is a call in progress for this chat %@", call); - CallViewController *callViewController = (CallViewController *) [UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController; - if (!callViewController.videoCall) { - [callViewController tapOnVideoCallkitWhenDeviceIsLocked]; - } + UIViewController *presentedVC = UIApplication.mnz_presentingViewController; + if ([presentedVC isKindOfClass:CallViewController.class]) { + CallViewController *callVC = (CallViewController *)UIApplication.mnz_presentingViewController; + if (!callVC.videoCall) { + [callVC tapOnVideoCallkitWhenDeviceIsLocked]; + } + } } else { MEGAChatConnection chatConnection = [[MEGASdkManager sharedMEGAChatSdk] chatConnectionState:self.chatRoom.chatId]; MEGALogDebug(@"Chat %@ connection state: %ld", [MEGASdk base64HandleForUserHandle:self.chatRoom.chatId], (long)chatConnection); @@ -612,8 +592,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct } else if ([userActivity.activityType isEqualToString:@"NSUserActivityTypeBrowsingWeb"]) { NSURL *universalLinkURL = userActivity.webpageURL; if (universalLinkURL) { - self.link = universalLinkURL; - + MEGALinkManager.linkURL = universalLinkURL; [self manageLink:[NSURL URLWithString:[NSString stringWithFormat:@"mega://%@", [universalLinkURL mnz_afterSlashesString]]]]; } } @@ -720,296 +699,31 @@ - (void)beginBackgroundTaskWithName:(NSString *)name { [self.backgroundTaskMutableDictionary setObject:name forKey:[NSNumber numberWithUnsignedInteger:backgroundTaskIdentifier]]; } -- (void)showOffline { - self.mainTBC.selectedIndex = MYACCOUNT; - MEGANavigationController *navigationController = [self.mainTBC.childViewControllers objectAtIndex:MYACCOUNT]; - MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; - [myAccountHallVC openOffline]; -} - -- (void)processSelectedOptionOnLink { - switch ([Helper selectedOptionOnLink]) { - case 1: { //Import file from link - MEGANode *node = [Helper linkNode]; - MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"BrowserNavigationControllerID"]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - - BrowserViewController *browserVC = navigationController.viewControllers.firstObject; - browserVC.selectedNodesArray = [NSArray arrayWithObject:node]; - [browserVC setBrowserAction:BrowserActionImport]; - break; - } - - case 2: { //Download file from link - MEGANode *node = [Helper linkNode]; - if (![Helper isFreeSpaceEnoughToDownloadNode:node isFolderLink:NO]) { - return; - } - [self showOffline]; - [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", nil)]; - [Helper downloadNode:node folderPath:[Helper relativePathForOffline] isFolderLink:NO shouldOverwrite:NO]; - break; - } - - case 3: { //Import folder or nodes from link - MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"BrowserNavigationControllerID"]; - BrowserViewController *browserVC = navigationController.viewControllers.firstObject; - [browserVC setBrowserAction:BrowserActionImportFromFolderLink]; - browserVC.selectedNodesArray = [NSArray arrayWithArray:[Helper nodesFromLinkMutableArray]]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - break; - } - - case 4: { //Download folder or nodes from link - for (MEGANode *node in [Helper nodesFromLinkMutableArray]) { - if (![Helper isFreeSpaceEnoughToDownloadNode:node isFolderLink:YES]) { - return; - } - } - [self showOffline]; - [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", nil)]; - for (MEGANode *node in [Helper nodesFromLinkMutableArray]) { - [Helper downloadNode:node folderPath:[Helper relativePathForOffline] isFolderLink:YES shouldOverwrite:NO]; - } - break; - } - - default: - break; - } - - [Helper setLinkNode:nil]; - [[Helper nodesFromLinkMutableArray] removeAllObjects]; - [Helper setSelectedOptionOnLink:0]; -} - - (void)manageLink:(NSURL *)url { if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { if (![LTHPasscodeViewController doesPasscodeExist] && isFetchNodesDone) { - [self processLink:url]; + [self showLink:url]; } } else { - if (![LTHPasscodeViewController doesPasscodeExist]) { - [self processLink:url]; - } - } -} - -- (void)processLink:(NSURL *)url { - if (self.window.rootViewController.presentedViewController) { - [self.window.rootViewController dismissViewControllerAnimated:NO completion:^{ - [self urlLinkType:url]; - }]; - } else { - [self urlLinkType:url]; - } -} - -- (void)urlLinkType:(NSURL *)url { - self.urlType = [url mnz_type]; - switch (self.urlType) { - case URLTypeDefault: - [Helper presentSafariViewControllerWithURL:self.link]; - self.link = nil; - - break; - - case URLTypeOpenInLink: - [self openIn]; - - break; - - case URLTypeFileLink: - [url mnz_showLinkView]; - self.link = nil; - - break; - - case URLTypeFolderLink: - [url mnz_showLinkView]; - self.link = nil; - - break; - - case URLTypeEncryptedLink: - [self showEncryptedLinkAlert:[url mnz_MEGAURL]]; - - break; - - case URLTypeConfirmationLink: { - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"alreadyLoggedInAlertTitle", @"Warning title shown when you try to confirm an account but you are logged in with another one") message:AMLocalizedString(@"alreadyLoggedInAlertMessage", @"Warning message shown when you try to confirm an account but you are logged in with another one") preferredStyle:UIAlertControllerStyleAlert]; - - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[MEGASdkManager sharedMEGASdk] logout]; - }]]; - - [UIApplication.mnz_visibleViewController presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; - } else { - [[MEGASdkManager sharedMEGASdk] querySignupLink:[url mnz_MEGAURL]]; - self.link = nil; - } - break; - } - - case URLTypeNewSignUpLink: - [[MEGASdkManager sharedMEGASdk] querySignupLink:[url mnz_MEGAURL]]; - - break; - - case URLTypeBackupLink: - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - [self showBackupLinkView]; - } else { - [self showPleaseLogInToYourAccountAlert]; - } - - break; - - case URLTypeIncomingPendingContactsLink: - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - [self showContactRequestsView]; - } else { - [self showPleaseLogInToYourAccountAlert]; - } - - break; - - case URLTypeChangeEmailLink: - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - [[MEGASdkManager sharedMEGASdk] queryChangeEmailLink:[url mnz_MEGAURL]]; - } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"needToBeLoggedInToCompleteYourEmailChange", @"Error message when a user attempts to change their email without an active login session.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - } - - break; - - case URLTypeCancelAccountLink: - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - [[MEGASdkManager sharedMEGASdk] queryCancelLink:[url mnz_MEGAURL]]; - } else { - [self showPleaseLogInToYourAccountAlert]; - } - - break; - - case URLTypeRecoverLink: - [[MEGASdkManager sharedMEGASdk] queryResetPasswordLink:[url mnz_MEGAURL]]; - - break; - - case URLTypeContactLink: - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - [url mnz_showLinkView]; - } else { - [self showPleaseLogInToYourAccountAlert]; - } - - break; - - case URLTypeChatLink: - self.mainTBC.selectedIndex = CHAT; - - break; - - case URLTypeLoginRequiredLink: { - NSString *session = [SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]; - if (session) { - [SAMKeychain deletePasswordForService:@"MEGA" account:@"sessionV3"]; - [SAMKeychain setPassword:session forService:@"MEGA" account:@"sessionV3"]; - } - - break; - } - - case URLTypeHandleLink: - self.nodeToPresentBase64Handle = [[url mnz_afterSlashesString] substringFromIndex:1]; - [self presentNode]; - - break; - - case URLTypeAchievementsLink: - [self openAchievements]; - break; - - default: - break; + [self showLink:url]; } } -- (void)dismissPresentedViews { - if (self.window.rootViewController.presentedViewController != nil) { - [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; - } -} - -- (void)showEncryptedLinkAlert:(NSString *)encryptedLinkURLString { - MEGAPasswordLinkRequestDelegate *delegate = [[MEGAPasswordLinkRequestDelegate alloc] initForDecryptionWithCompletion:^(MEGARequest *request) { - NSString *url = [NSString stringWithFormat:@"mega://%@", [[request.text componentsSeparatedByString:@"/"] lastObject]]; - [self processLink:[NSURL URLWithString:url]]; - } onError:^(MEGARequest *request) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"decryptionKeyNotValid", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { - [self showEncryptedLinkAlert:request.link]; - }]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - }]; +- (void)showLink:(NSURL *)url { + if (!MEGALinkManager.linkURL) return; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"decryptionKeyAlertTitle", nil) message:AMLocalizedString(@"decryptionKeyAlertMessage", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { - textField.placeholder = AMLocalizedString(@"decryptionKey", nil); + [self dismissPresentedViewsAndDo:^{ + [MEGALinkManager processLinkURL:url]; }]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[MEGASdkManager sharedMEGASdk] decryptPasswordProtectedLink:encryptedLinkURLString password:alertController.textFields.firstObject.text delegate:delegate]; - }]]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - - self.link = nil; -} - -- (void)showBackupLinkView { - MasterKeyViewController *masterKeyVC = [[UIStoryboard storyboardWithName:@"Settings" bundle:nil] instantiateViewControllerWithIdentifier:@"MasterKeyViewControllerID"]; - masterKeyVC.navigationItem.rightBarButtonItem = [self cancelBarButtonItem]; - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:masterKeyVC]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; } -- (void)showContactRequestsView { - ContactRequestsViewController *contactsRequestsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsRequestsViewControllerID"]; - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:contactsRequestsVC]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; -} - -- (void)openAchievements { - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - MainTabBarController *mainTBC = (MainTabBarController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; - mainTBC.selectedIndex = MYACCOUNT; - MEGANavigationController *navigationController = [mainTBC.childViewControllers objectAtIndex:MYACCOUNT]; - MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; - if ([[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { - [myAccountHallVC openAchievements]; - } +- (void)dismissPresentedViewsAndDo:(void (^)(void))completion { + if (self.window.rootViewController.presentedViewController) { + [self.window.rootViewController dismissViewControllerAnimated:NO completion:^{ + if (completion) completion(); + }]; } else { - [self showPleaseLogInToYourAccountAlert]; - } -} - -- (void)openIn { - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - MEGANavigationController *browserNavigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"BrowserNavigationControllerID"]; - BrowserViewController *browserVC = browserNavigationController.viewControllers.firstObject; - [browserVC setLocalpath:[self.link path]]; // "file://" = 7 characters - [browserVC setBrowserAction:BrowserActionOpenIn]; - - [UIApplication.mnz_visibleViewController presentViewController:browserNavigationController animated:YES completion:nil]; - - self.link = nil; + if (completion) completion(); } } @@ -1034,7 +748,7 @@ - (BOOL)manageQuickActionType:(NSString *)type { CloudDriveViewController *cloudDriveVC = navigationController.viewControllers.firstObject; [cloudDriveVC presentUploadAlertController]; } else if ([type isEqualToString:@"mega.ios.offline"]) { - [self showOffline]; + [self.mainTBC showOffline]; } else { quickActionManaged = NO; } @@ -1044,23 +758,6 @@ - (BOOL)manageQuickActionType:(NSString *)type { return quickActionManaged; } -- (void)removeUnfinishedTransfersOnFolder:(NSString *)directory { - NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directory error:nil]; - for (NSString *item in directoryContents) { - NSDictionary *attributesDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[directory stringByAppendingPathComponent:item] error:nil]; - if ([attributesDictionary objectForKey:NSFileType] == NSFileTypeDirectory) { - [self removeUnfinishedTransfersOnFolder:[directory stringByAppendingPathComponent:item]]; - } else { - if ([item.pathExtension.lowercaseString isEqualToString:@"mega"]) { - NSError *error = nil; - if (![[NSFileManager defaultManager] removeItemAtPath:[directory stringByAppendingPathComponent:item] error:&error]) { - MEGALogError(@"Remove item at path failed with error: %@", error); - } - } - } - } -} - - (void)startTimerAPI_EAGAIN { timerAPI_EAGAIN = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(showServersTooBusy) userInfo:nil repeats:NO]; } @@ -1116,7 +813,7 @@ - (void)showServersTooBusy { - (void)showOverquotaAlert { [self disableCameraUploads]; - if (!UIApplication.mnz_visibleViewController.presentedViewController || UIApplication.mnz_visibleViewController.presentedViewController != self.overquotaAlertView) { + if (!UIApplication.mnz_presentingViewController.presentedViewController || UIApplication.mnz_presentingViewController.presentedViewController != self.overquotaAlertView) { isOverquota = YES; [[MEGASdkManager sharedMEGASdk] getAccountDetails]; } @@ -1129,71 +826,6 @@ - (void)disableCameraUploads { } } -- (void)showLinkNotValid { - [self showEmptyStateViewWithImageNamed:@"invalidFileLink" title:AMLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid") text:@""]; - self.link = nil; - self.urlType = URLTypeDefault; -} - -- (void)showEmptyStateViewWithImageNamed:(NSString *)imageName title:(NSString *)title text:(NSString *)text { - UnavailableLinkView *unavailableLinkView = [[[NSBundle mainBundle] loadNibNamed:@"UnavailableLinkView" owner:self options: nil] firstObject]; - [unavailableLinkView.imageView setImage:[UIImage imageNamed:imageName]]; - [unavailableLinkView.imageView setContentMode:UIViewContentModeScaleAspectFit]; - [unavailableLinkView.titleLabel setText:title]; - unavailableLinkView.textLabel.text = text; - [unavailableLinkView setFrame:self.window.frame]; - - UIViewController *viewController = [[UIViewController alloc] init]; - [viewController.view addSubview:unavailableLinkView]; - [viewController.navigationItem setTitle:title]; - [viewController.navigationItem setRightBarButtonItem:[self cancelBarButtonItem]]; - - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:viewController]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; -} - -- (UIBarButtonItem *)cancelBarButtonItem { - UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:AMLocalizedString(@"cancel", nil) style:UIBarButtonItemStylePlain target:nil action:@selector(dismissPresentedViews)]; - return cancelBarButtonItem; -} - -- (void)showPleaseLogInToYourAccountAlert { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"pleaseLogInToYourAccount", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; -} - -- (void)presentConfirmViewControllerType:(ConfirmType)confirmType link:(NSString *)link email:(NSString *)email { - MEGANavigationController *confirmAccountNavigationController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ConfirmAccountNavigationControllerID"]; - - ConfirmAccountViewController *confirmAccountVC = confirmAccountNavigationController.viewControllers.firstObject; - confirmAccountVC.confirmType = confirmType; - confirmAccountVC.confirmationLinkString = link; - confirmAccountVC.emailString = email; - - [UIApplication.mnz_visibleViewController presentViewController:confirmAccountNavigationController animated:YES completion:nil]; -} - -- (void)presentChangeViewType:(ChangeType)changeType email:(NSString *)email masterKey:(NSString *)masterKey link:(NSString *)link { - ChangePasswordViewController *changePasswordVC = [[UIStoryboard storyboardWithName:@"Settings" bundle:nil] instantiateViewControllerWithIdentifier:@"ChangePasswordViewControllerID"]; - changePasswordVC.changeType = changeType; - changePasswordVC.email = email; - changePasswordVC.masterKey = masterKey; - changePasswordVC.link = link; - - MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:changePasswordVC]; - [navigationController addCancelButton]; - - UIViewController *visibleViewController = UIApplication.mnz_visibleViewController; - if ([visibleViewController isKindOfClass:UIAlertController.class]) { - [visibleViewController dismissViewControllerAnimated:NO completion:^{ - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - }]; - } else { - [visibleViewController presentViewController:navigationController animated:YES completion:nil]; - } -} - - (void)requestUserName { if (![[MEGAStore shareInstance] fetchUserWithUserHandle:[[[MEGASdkManager sharedMEGASdk] myUser] handle]]) { [[MEGASdkManager sharedMEGASdk] getUserAttributeType:MEGAUserAttributeFirstname]; @@ -1234,29 +866,26 @@ - (void)showMainTabBar { } if (![LTHPasscodeViewController doesPasscodeExist]) { - if (self.nodeToPresentBase64Handle) { - [self presentNode]; + if (MEGALinkManager.nodeToPresentBase64Handle) { + [MEGALinkManager presentNode]; } if (isAccountFirstLogin) { isAccountFirstLogin = NO; - if (self.urlType == URLTypeConfirmationLink) { + if (self.isNewAccount) { if ([MEGAPurchase sharedInstance].products.count > 0) { [self showChooseAccountType]; } else { [[MEGAPurchase sharedInstance] setPricingsDelegate:self]; self.chooseAccountTypeLater = YES; } + self.newAccount = NO; } - - if ([Helper selectedOptionOnLink] != 0) { - [self processSelectedOptionOnLink]; - } + + [MEGALinkManager processSelectedOptionOnLink]; } - if (self.link != nil) { - [self processLink:self.link]; - } + [self showLink:MEGALinkManager.linkURL]; [self manageQuickActionType:self.quickActionType]; } @@ -1336,105 +965,6 @@ - (void)registerForNotifications { } } -- (void)presentNode { - uint64_t handle = [MEGASdk handleForBase64Handle:self.nodeToPresentBase64Handle]; - MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:handle]; - if (node) { - UINavigationController *navigationController; - if ([[MEGASdkManager sharedMEGASdk] accessLevelForNode:node] != MEGAShareTypeAccessOwner) { // node from inshare - self.mainTBC.selectedIndex = SHARES; - SharedItemsViewController *sharedItemsVC = self.mainTBC.childViewControllers[SHARES].childViewControllers[0]; - [sharedItemsVC selectSegment:0]; // Incoming - } else { - self.mainTBC.selectedIndex = CLOUD; - } - navigationController = [self.mainTBC.childViewControllers objectAtIndex:self.mainTBC.selectedIndex]; - - [self presentNode:node inNavigationController:navigationController]; - } else { - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - UIAlertController *theContentIsNotAvailableAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"theContentIsNotAvailableForThisAccount", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [theContentIsNotAvailableAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - - [theContentIsNotAvailableAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"logoutLabel", @"Title of the button which logs out from your account.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - NSError *error; - NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] error:&error]; - if (error) { - MEGALogError(@"Contents of directory at path failed with error: %@", error); - } - - BOOL isInboxDirectory = NO; - for (NSString *directoryElement in directoryContent) { - if ([directoryElement isEqualToString:@"Inbox"]) { - NSString *inboxPath = [[Helper pathForOffline] stringByAppendingPathComponent:@"Inbox"]; - [[NSFileManager defaultManager] fileExistsAtPath:inboxPath isDirectory:&isInboxDirectory]; - break; - } - } - - if (directoryContent.count > 0) { - if (directoryContent.count == 1 && isInboxDirectory) { - [[MEGASdkManager sharedMEGASdk] logout]; - return; - } - - UIAlertController *warningAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"warning", nil) message:AMLocalizedString(@"allFilesSavedForOfflineWillBeDeletedFromYourDevice", @"Alert message shown when the user perform logout and has files in the Offline directory") preferredStyle:UIAlertControllerStyleAlert]; - [warningAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; - [warningAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"logoutLabel", @"Title of the button which logs out from your account.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[MEGASdkManager sharedMEGASdk] logout]; - }]]; - - [UIApplication.mnz_visibleViewController presentViewController:warningAlertController animated:YES completion:nil]; - } else { - [[MEGASdkManager sharedMEGASdk] logout]; - } - }]]; - - [UIApplication.mnz_visibleViewController presentViewController:theContentIsNotAvailableAlertController animated:YES completion:nil]; - } - } - self.nodeToPresentBase64Handle = nil; -} - -- (void)presentNode:(MEGANode *)node inNavigationController:(UINavigationController *)navigationController { - [navigationController popToRootViewControllerAnimated:NO]; - - NSArray *parentTreeArray = node.mnz_parentTreeArray; - for (MEGANode *node in parentTreeArray) { - CloudDriveViewController *cloudDriveVC = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"CloudDriveID"]; - cloudDriveVC.parentNode = node; - [navigationController pushViewController:cloudDriveVC animated:NO]; - } - - switch (node.type) { - case MEGANodeTypeFolder: - case MEGANodeTypeRubbish: { - CloudDriveViewController *cloudDriveVC = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"CloudDriveID"]; - cloudDriveVC.parentNode = node; - [navigationController pushViewController:cloudDriveVC animated:NO]; - break; - } - - case MEGANodeTypeFile: { - if (node.name.mnz_isImagePathExtension || node.name.mnz_isVideoPathExtension) { - MEGANode *parentNode = [[MEGASdkManager sharedMEGASdk] nodeForHandle:node.parentHandle]; - MEGANodeList *nodeList = [[MEGASdkManager sharedMEGASdk] childrenForParent:parentNode]; - NSMutableArray *mediaNodesArray = [nodeList mnz_mediaNodesMutableArrayFromNodeList]; - - MEGAPhotoBrowserViewController *photoBrowserVC = [MEGAPhotoBrowserViewController photoBrowserWithMediaNodes:mediaNodesArray api:[MEGASdkManager sharedMEGASdk] displayMode:DisplayModeCloudDrive presentingNode:node preferredIndex:0]; - - [navigationController presentViewController:photoBrowserVC animated:YES completion:nil]; - } else { - [node mnz_openNodeInNavigationController:navigationController folderLink:NO]; - } - break; - } - - default: - break; - } -} - - (void)migrateLocalCachesLocation { NSString *cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; NSError *error; @@ -1487,11 +1017,7 @@ - (void)copyDatabasesForExtensions { for (NSString *filename in applicationSupportContent) { if ([filename containsString:@"megaclient"] || [filename containsString:@"karere"]) { NSString *destinationPath = [groupSupportPath stringByAppendingPathComponent:filename]; - if ([fileManager fileExistsAtPath:destinationPath]) { - if (![fileManager removeItemAtPath:destinationPath error:&error]) { - MEGALogError(@"Remove item at path failed with error: %@", error); - } - } + [NSFileManager.defaultManager mnz_removeItemAtPath:destinationPath]; if (![fileManager copyItemAtPath:[applicationSupportDirectoryString stringByAppendingPathComponent:filename] toPath:destinationPath error:&error]) { MEGALogError(@"Copy item at path failed with error: %@", error); } @@ -1559,20 +1085,11 @@ - (void)presentInviteContactCustomAlertViewController { }; } - [UIApplication.mnz_visibleViewController presentViewController:customModalAlertVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; self.presentInviteContactVCLater = NO; } -- (void)openChatRoomWithChatNumber:(NSNumber *)chatNumber { - if (chatNumber) { - self.mainTBC.selectedIndex = CHAT; - MEGANavigationController *navigationController = [[self.mainTBC viewControllers] objectAtIndex:CHAT]; - ChatRoomsViewController *chatRoomsVC = navigationController.viewControllers.firstObject; - [chatRoomsVC openChatRoomWithID:chatNumber.unsignedLongLongValue]; - } -} - - (void)application:(UIApplication *)application shouldHideWindows:(BOOL)shouldHide { for (UIWindow *window in application.windows) { if ([NSStringFromClass(window.class) isEqualToString:@"UIRemoteKeyboardWindow"] || [NSStringFromClass(window.class) isEqualToString:@"UITextEffectsWindow"]) { @@ -1586,8 +1103,7 @@ - (void)showChooseAccountType { MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; upgradeTVC.chooseAccountType = YES; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - self.urlType = URLTypeDefault; + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; } #pragma mark - Battery changed @@ -1610,12 +1126,10 @@ - (void)passcodeWasEnteredSuccessfully { [self.window setRootViewController:_mainTBC]; [[UIApplication sharedApplication] setStatusBarHidden:NO]; } else { - if (self.link != nil) { - [self processLink:self.link]; - } + [self showLink:MEGALinkManager.linkURL]; - if (self.nodeToPresentBase64Handle) { - [self presentNode]; + if (MEGALinkManager.nodeToPresentBase64Handle) { + [MEGALinkManager presentNode]; } [self manageQuickActionType:self.quickActionType]; @@ -1646,9 +1160,7 @@ - (void)renameAttributesAtPath:(NSString *)v2Path v3Path:(NSString *)v3Path { if ([base64Filename isEqualToString:@"AAAAAAAA"]) { if (attributePath.mnz_isImagePathExtension) { - if ([[NSFileManager defaultManager] fileExistsAtPath:attributePath]) { - [[NSFileManager defaultManager] removeItemAtPath:attributePath error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:attributePath]; } else { NSString *newAttributePath = [v3Path stringByAppendingPathComponent:attributeFilename]; [[NSFileManager defaultManager] moveItemAtPath:attributePath toPath:newAttributePath error:nil]; @@ -1663,24 +1175,18 @@ - (void)renameAttributesAtPath:(NSString *)v2Path v3Path:(NSString *)v3Path { directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:v2Path error:nil]; if ([directoryContent count] == 0) { - if ([[NSFileManager defaultManager] fileExistsAtPath:v2Path]) { - [[NSFileManager defaultManager] removeItemAtPath:v2Path error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:v2Path]; } } - (void)cameraUploadsSettingsCompatibility { // PhotoSync old location of completed uploads NSString *oldCompleted = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"PhotoSync/completed.plist"]; - if ([[NSFileManager defaultManager] fileExistsAtPath:oldCompleted]) { - [[NSFileManager defaultManager] removeItemAtPath:oldCompleted error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:oldCompleted]; // PhotoSync v2 location of completed uploads NSString *v2Completed = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"PhotoSync/com.plist"]; - if ([[NSFileManager defaultManager] fileExistsAtPath:v2Completed]) { - [[NSFileManager defaultManager] removeItemAtPath:v2Completed error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:v2Completed]; // PhotoSync settings NSString *oldPspPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"PhotoSync/psp.plist"]; @@ -1700,22 +1206,13 @@ - (void)cameraUploadsSettingsCompatibility { BOOL videoEnabled = [cameraUploadsSettings objectForKey:@"videoEnabled"]; [NSUserDefaults.standardUserDefaults setObject:@(videoEnabled) forKey:kIsUploadVideosEnabled]; - [[NSFileManager defaultManager] removeItemAtPath:v2PspPath error:nil]; + [NSFileManager.defaultManager mnz_removeItemAtPath:v2PspPath]; } } - (void)removeOldStateCache { NSString *libraryDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:libraryDirectory error:nil]; - - for (NSString *item in directoryContent) { - if([item.pathExtension isEqualToString:@"db"]) { - NSString *stateCachePath = [libraryDirectory stringByAppendingPathComponent:item]; - if ([[NSFileManager defaultManager] fileExistsAtPath:stateCachePath]) { - [[NSFileManager defaultManager] removeItemAtPath:stateCachePath error:nil]; - } - } - } + [NSFileManager.defaultManager mnz_removeFolderContentsRecursivelyAtPath:libraryDirectory forItemsExtension:@"db"]; } - (void)languageCompatibility { @@ -1788,9 +1285,35 @@ - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPush - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { MEGALogDebug(@"Did receive incoming push with payload: %@", [payload dictionaryPayload]); - if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) { + // Call + if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground && [[[payload dictionaryPayload] objectForKey:@"megatype"] integerValue] == 4) { [self beginBackgroundTaskWithName:@"VoIP"]; } + + // Message + if ([[[payload dictionaryPayload] objectForKey:@"megatype"] integerValue] == 2) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"VoIP_messages"]; + NSString *chatIdB64 = [[[payload dictionaryPayload] objectForKey:@"megadata"] objectForKey:@"chatid"]; + NSString *msgIdB64 = [[[payload dictionaryPayload] objectForKey:@"megadata"] objectForKey:@"msgid"]; + NSString *silent = [[[payload dictionaryPayload] objectForKey:@"megadata"] objectForKey:@"silent"]; + if (chatIdB64 && msgIdB64) { + uint64_t chatId = [MEGASdk handleForBase64UserHandle:chatIdB64]; + uint64_t msgId = [MEGASdk handleForBase64UserHandle:msgIdB64]; + MEGAChatMessage *message = [[MEGASdkManager sharedMEGAChatSdk] messageForChat:chatId messageId:msgId]; + MEGAChatRoom *chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:chatId]; + + [[MEGASdkManager sharedMEGAChatSdk] pushReceivedWithBeep:YES chatId:chatId]; + + if (chatRoom && message && [UIApplication sharedApplication].applicationState != UIApplicationStateActive) { + MEGALocalNotificationManager *localNotificationManager = [[MEGALocalNotificationManager alloc] initWithChatRoom:chatRoom message:message silent:NO]; + [localNotificationManager proccessNotification]; + } else { + [[MEGAStore shareInstance] insertMessage:msgId chatId:chatId]; + } + } else if (silent) { + [[MEGASdkManager sharedMEGAChatSdk] pushReceivedWithBeep:NO chatId:~(uint64_t)0]; + } + } } #pragma mark - UNUserNotificationCenterDelegate @@ -1799,7 +1322,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti MEGALogDebug(@"userNotificationCenter didReceiveNotificationResponse %@", response); [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[response.notification.request.identifier]]; - [self openChatRoomWithChatNumber:response.notification.request.content.userInfo[@"chatId"]]; + [self.mainTBC openChatRoomNumber:response.notification.request.content.userInfo[@"chatId"]]; completionHandler(); } @@ -1811,7 +1334,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNot - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { if (@available(iOS 10, *)) {} else { if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) { - [self openChatRoomWithChatNumber:notification.userInfo[@"chatId"]]; + [self.mainTBC openChatRoomNumber:notification.userInfo[@"chatId"]]; } } } @@ -1855,9 +1378,7 @@ - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList { if ([user hasChangedType:MEGAUserChangeTypeAvatar]) { //If you have changed your avatar, remove the old and request the new one NSString *userBase64Handle = [MEGASdk base64HandleForUserHandle:user.handle]; NSString *avatarFilePath = [[Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"] stringByAppendingPathComponent:userBase64Handle]; - if ([[NSFileManager defaultManager] fileExistsAtPath:avatarFilePath]) { - [[NSFileManager defaultManager] removeItemAtPath:avatarFilePath error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:avatarFilePath]; [[MEGASdkManager sharedMEGASdk] getAvatarUser:user destinationFilePath:avatarFilePath]; } @@ -1878,9 +1399,7 @@ - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList { if ([user hasChangedType:MEGAUserChangeTypeAvatar]) { NSString *userBase64Handle = [MEGASdk base64HandleForUserHandle:user.handle]; NSString *avatarFilePath = [[Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"] stringByAppendingPathComponent:userBase64Handle]; - if ([[NSFileManager defaultManager] fileExistsAtPath:avatarFilePath]) { - [[NSFileManager defaultManager] removeItemAtPath:avatarFilePath error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:avatarFilePath]; [[MEGASdkManager sharedMEGASdk] getAvatarUser:user destinationFilePath:avatarFilePath]; } if ([user hasChangedType:MEGAUserChangeTypeFirstname]) { @@ -1923,7 +1442,7 @@ - (void)onNodesUpdate:(MEGASdk *)api nodeList:(MEGANodeList *)nodeList { } else { NSArray *nodesToIndex = [nodeList mnz_nodesArrayFromNodeList]; - MEGALogDebug(@"Spotlight indexing %lu nodes updated", nodesToIndex.count); + MEGALogDebug(@"Spotlight indexing %tu nodes updated", nodesToIndex.count); for (MEGANode *node in nodesToIndex) { [self.indexer index:node]; } @@ -1966,7 +1485,7 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { } case MEGARequestTypeLogout: { - if (self.urlType == URLTypeCancelAccountLink) { + if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { return; } @@ -2012,56 +1531,28 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ([request type] == MEGARequestTypeLogin) { [Helper logout]; [self showOnboarding]; - } else if ([request type] == MEGARequestTypeQuerySignUpLink) { - [self showLinkNotValid]; - } - break; - } - - case MEGAErrorTypeApiEExpired: { - if (request.type == MEGARequestTypeQueryRecoveryLink || request.type == MEGARequestTypeConfirmRecoveryLink) { - NSString *alertTitle; - if (self.urlType == URLTypeCancelAccountLink) { - alertTitle = AMLocalizedString(@"cancellationLinkHasExpired", @"During account cancellation (deletion)"); - } else if (self.urlType == URLTypeRecoverLink) { - alertTitle = AMLocalizedString(@"recoveryLinkHasExpired", @"Message shown during forgot your password process if the link to reset password has expired"); - } - UIAlertController *linkHasExpiredAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:nil preferredStyle:UIAlertControllerStyleAlert]; - [linkHasExpiredAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:linkHasExpiredAlertController animated:YES completion:nil]; - } - break; - } - - case MEGAErrorTypeApiENoent: { - if ([request type] == MEGARequestTypeQuerySignUpLink) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - } else if ([request type] == MEGARequestTypeQueryRecoveryLink) { - [self showLinkNotValid]; } break; } case MEGAErrorTypeApiESid: { - if (self.urlType == URLTypeCancelAccountLink) { - self.urlType = URLTypeDefault; + if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { [Helper logout]; [self showOnboarding]; UIAlertController *accountCanceledSuccessfullyAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"accountCanceledSuccessfully", @"During account cancellation (deletion)") message:nil preferredStyle:UIAlertControllerStyleAlert]; [accountCanceledSuccessfullyAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; - - [UIApplication.mnz_visibleViewController presentViewController:accountCanceledSuccessfullyAlertController animated:YES completion:nil]; + [UIApplication.mnz_visibleViewController presentViewController:accountCanceledSuccessfullyAlertController animated:YES completion:^{ + [MEGALinkManager resetLinkAndURLType]; + }]; return; } if ([request type] == MEGARequestTypeLogin || [request type] == MEGARequestTypeLogout) { - if (!self.API_ESIDAlertController || UIApplication.mnz_visibleViewController.presentedViewController != self.API_ESIDAlertController) { + if (!self.API_ESIDAlertController || UIApplication.mnz_presentingViewController.presentedViewController != self.API_ESIDAlertController) { self.API_ESIDAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"loggedOut_alertTitle", nil) message:AMLocalizedString(@"loggedOutFromAnotherLocation", nil) preferredStyle:UIAlertControllerStyleAlert]; [self.API_ESIDAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:self.API_ESIDAlertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:self.API_ESIDAlertController animated:YES completion:nil]; [Helper logout]; [self showOnboarding]; } @@ -2079,19 +1570,14 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ([request type] == MEGARequestTypeSetAttrFile) { MEGANode *node = [api nodeForHandle:request.nodeHandle]; NSString *thumbnailFilePath = [Helper pathForNode:node inSharedSandboxCacheDirectory:@"thumbnailsV3"]; - BOOL thumbnailExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; - if (thumbnailExists) { - [[NSFileManager defaultManager] removeItemAtPath:thumbnailFilePath error:nil]; - } + [NSFileManager.defaultManager mnz_removeItemAtPath:thumbnailFilePath]; } break; } case MEGAErrorTypeApiEIncomplete: { - if (request.type == MEGARequestTypeQuerySignUpLink) { - [self showLinkNotValid]; - } else if (request.type == MEGARequestTypeLogout && request.paramType == MEGAErrorTypeApiESSL && !self.sslKeyPinningController) { + if (request.type == MEGARequestTypeLogout && request.paramType == MEGAErrorTypeApiESSL && !self.sslKeyPinningController) { [SVProgressHUD dismiss]; _sslKeyPinningController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"sslUnverified_alertTitle", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ignore", @"Button title to allow the user ignore something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { @@ -2116,7 +1602,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } }]]; - [[UIApplication mnz_visibleViewController] presentViewController:self.sslKeyPinningController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:self.sslKeyPinningController animated:YES completion:nil]; } break; } @@ -2125,7 +1611,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ([request type] == MEGARequestTypeLogin || [request type] == MEGARequestTypeFetchNodes) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:AMLocalizedString(@"accountBlocked", @"Error message when trying to login and the account is blocked") preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; [api logout]; } @@ -2148,7 +1634,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG isFetchNodesDone = NO; } else { isAccountFirstLogin = YES; - self.link = nil; + self.newAccount = (MEGALinkManager.urlType == URLTypeConfirmationLink); + [MEGALinkManager resetLinkAndURLType]; } [[MEGASdkManager sharedMEGASdk] fetchNodes]; break; @@ -2176,6 +1663,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ([[NSUserDefaults standardUserDefaults] boolForKey:@"IsChatEnabled"] || isAccountFirstLogin) { [[MEGASdkManager sharedMEGAChatSdk] addChatDelegate:self.mainTBC]; + + MEGAChatNotificationDelegate *chatNotificationDelegate = [MEGAChatNotificationDelegate new]; + [[MEGASdkManager sharedMEGAChatSdk] addChatNotificationDelegate:chatNotificationDelegate]; if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { [[MEGASdkManager sharedMEGAChatSdk] connectInBackground]; @@ -2207,101 +1697,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG isOverquota = NO; [[MEGASdkManager sharedMEGASdk] getAccountDetails]; [self copyDatabasesForExtensions]; + [[NSUserDefaults standardUserDefaults] setBool:[api appleVoipPushEnabled] forKey:@"VoIP_messages"]; break; } - case MEGARequestTypeQuerySignUpLink: { - if (self.urlType == URLTypeConfirmationLink) { - if (request.flag) { - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionId"]) { - MEGALoginRequestDelegate *loginRequestDelegate = [[MEGALoginRequestDelegate alloc] init]; - loginRequestDelegate.confirmAccountInOtherClient = YES; - NSString *base64pwkey = [SAMKeychain passwordForService:@"MEGA" account:@"base64pwkey"]; - NSString *stringHash = [api hashForBase64pwkey:base64pwkey email:request.email]; - [api fastLoginWithEmail:request.email stringHash:stringHash base64pwKey:base64pwkey delegate:loginRequestDelegate]; - } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"accountAlreadyConfirmed", @"Message shown when the user clicks on a confirm account link that has already been used") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { - MEGANavigationController *navigationController = (MEGANavigationController *)self.window.rootViewController; - if ([navigationController.topViewController isKindOfClass:[LoginViewController class]]) { - LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; - loginVC.emailString = request.email; - [loginVC viewWillAppear:NO]; - } - }]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - } - } else { - [self presentConfirmViewControllerType:ConfirmTypeAccount link:request.link email:request.email]; - } - } else if (self.urlType == URLTypeNewSignUpLink) { - - if ([[MEGASdkManager sharedMEGASdk] isLoggedIn]) { - _emailOfNewSignUpLink = [request email]; - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"alreadyLoggedInAlertTitle", nil) message:AMLocalizedString(@"alreadyLoggedInAlertMessage", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { - _emailOfNewSignUpLink = nil; - }]]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) { - [[MEGASdkManager sharedMEGASdk] logout]; - }]]; - [UIApplication.mnz_visibleViewController presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; - } else { - if ([self.window.rootViewController isKindOfClass:[MEGANavigationController class]]) { - MEGANavigationController *navigationController = (MEGANavigationController *)self.window.rootViewController; - - if ([navigationController.topViewController isKindOfClass:[LoginViewController class]]) { - LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; - [loginVC performSegueWithIdentifier:@"CreateAccountStoryboardSegueID" sender:[request email]]; - _emailOfNewSignUpLink = nil; - } else if ([navigationController.topViewController isKindOfClass:[CreateAccountViewController class]]) { - CreateAccountViewController *createAccountVC = (CreateAccountViewController *)navigationController.topViewController; - [createAccountVC setEmailString:[request email]]; - [createAccountVC viewDidLoad]; - } - } - } - } - break; - } - - case MEGARequestTypeQueryRecoveryLink: { - if (self.urlType == URLTypeChangeEmailLink) { - [self presentConfirmViewControllerType:ConfirmTypeEmail link:request.link email:request.email]; - } else if (self.urlType == URLTypeCancelAccountLink) { - [self presentConfirmViewControllerType:ConfirmTypeCancelAccount link:request.link email:request.email]; - } else if (self.urlType == URLTypeRecoverLink) { - if (request.flag) { - UIAlertController *masterKeyLoggedInAlertController; - if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:AMLocalizedString(@"youRecoveryKeyIsGoingTo", @"Text of the alert after opening the recovery link to reset pass being logged.") preferredStyle:UIAlertControllerStyleAlert]; - } else { - masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:AMLocalizedString(@"pleaseEnterYourRecoveryKey", @"A message shown to explain that the user has to input (type or paste) their recovery key to continue with the reset password process.") preferredStyle:UIAlertControllerStyleAlert]; - [masterKeyLoggedInAlertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { - textField.placeholder = AMLocalizedString(@"recoveryKey", @"Label for any 'Recovery Key' button, link, text, title, etc. Preserve uppercase - (String as short as possible). The Recovery Key is the new name for the account 'Master Key', and can unlock (recover) the account if the user forgets their password."); - }]; - } - - [masterKeyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [masterKeyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - NSString *masterKey = masterKeyLoggedInAlertController.textFields.count ? masterKeyLoggedInAlertController.textFields[0].text : [[MEGASdkManager sharedMEGASdk] masterKey]; - [self presentChangeViewType:ChangeTypeResetPassword email:self.emailOfNewSignUpLink masterKey:masterKey link:self.recoveryLink]; - self.emailOfNewSignUpLink = nil; - self.recoveryLink = nil; - }]]; - - self.emailOfNewSignUpLink = request.email; - self.recoveryLink = request.link; - - [UIApplication.mnz_visibleViewController presentViewController:masterKeyLoggedInAlertController animated:YES completion:nil]; - } else { - [self presentChangeViewType:ChangeTypeParkAccount email:request.email masterKey:nil link:request.link]; - } - } - break; - } - case MEGARequestTypeLogout: { [Helper logout]; [self showOnboarding]; @@ -2311,24 +1711,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (self.messageForSuspendedAccount) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:self.messageForSuspendedAccount preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; - } - - if ((self.urlType == URLTypeConfirmationLink) && self.link) { - [[MEGASdkManager sharedMEGASdk] querySignupLink:self.link.mnz_MEGAURL]; - self.link = nil; - } - - if ((self.urlType == URLTypeNewSignUpLink) && (_emailOfNewSignUpLink != nil)) { - if ([self.window.rootViewController isKindOfClass:[MEGANavigationController class]]) { - MEGANavigationController *navigationController = (MEGANavigationController *)self.window.rootViewController; - - if ([navigationController.topViewController isKindOfClass:[LoginViewController class]]) { - LoginViewController *loginVC = (LoginViewController *)navigationController.topViewController; - [loginVC performSegueWithIdentifier:@"CreateAccountStoryboardSegueID" sender:_emailOfNewSignUpLink]; - _emailOfNewSignUpLink = nil; - } - } + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } break; } @@ -2345,15 +1728,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; - if (self.window.rootViewController.presentedViewController) { - [self.window.rootViewController dismissViewControllerAnimated:YES completion:^{ - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - }]; - } else { - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; - } + [self dismissPresentedViewsAndDo:^{ + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; + }]; }]]; - [UIApplication.mnz_visibleViewController presentViewController:self.overquotaAlertView animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:self.overquotaAlertView animated:YES completion:nil]; isOverquota = NO; } @@ -2463,31 +1842,28 @@ - (void)onChatRequestStart:(MEGAChatSdk *)api request:(MEGAChatRequest *)request - (void)onChatRequestFinish:(MEGAChatSdk *)api request:(MEGAChatRequest *)request error:(MEGAChatError *)error { if ([error type] != MEGAChatErrorTypeOk) { - MEGALogError(@"onChatRequestFinish error type: %ld request type: %ld", error.type, request.type); + MEGALogError(@"onChatRequestFinish error type: %td request type: %td", error.type, request.type); return; } if (request.type == MEGAChatRequestTypeLogout) { - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"logging"]) { - [[MEGALogger sharedLogger] enableSDKlogs]; - } [MEGASdkManager destroySharedMEGAChatSdk]; [self.mainTBC setBadgeValueForChats]; } - MEGALogInfo(@"onChatRequestFinish request type: %ld", request.type); + MEGALogInfo(@"onChatRequestFinish request type: %td", request.type); } #pragma mark - MEGAChatDelegate - (void)onChatInitStateUpdate:(MEGAChatSdk *)api newState:(MEGAChatInit)newState { - MEGALogInfo(@"onChatInitStateUpdate new state: %ld", newState); + MEGALogInfo(@"onChatInitStateUpdate new state: %td", newState); if (newState == MEGAChatInitError) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"error", nil) message:@"Chat disabled (Init error). Enable chat in More -> Settings -> Chat" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; [[MEGASdkManager sharedMEGAChatSdk] logout]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } @@ -2555,7 +1931,7 @@ - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfe UpgradeTableViewController *upgradeTVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"UpgradeID"]; MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:upgradeTVC]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; } else { // Redirect to my account if the products are not available [self.mainTBC setSelectedIndex:4]; @@ -2563,7 +1939,7 @@ - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfe }]; }; - [UIApplication.mnz_visibleViewController presentViewController:customModalAlertVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:customModalAlertVC animated:YES completion:nil]; } } @@ -2604,6 +1980,11 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: [transfer mnz_parseAppData]; + if ([transfer.appData containsString:@">localIdentifier"]) { + NSString *localIdentifier = [transfer.appData mnz_stringBetweenString:@">localIdentifier=" andString:@""]; + [[Helper uploadingNodes] removeObject:localIdentifier]; + } + [Helper startPendingUploadTransferIfNeeded]; } @@ -2633,7 +2014,7 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: return; } - MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:node api:[MEGASdkManager sharedMEGASdk]]; + MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:node]; if (!offlineNodeExist) { NSRange replaceRange = [transfer.path rangeOfString:@"Documents/"]; if (replaceRange.location != NSNotFound) { diff --git a/iMEGA/Camera uploads/CameraUploads.m b/iMEGA/Camera uploads/CameraUploads.m index faf941388f..fcbc5fdc8c 100644 --- a/iMEGA/Camera uploads/CameraUploads.m +++ b/iMEGA/Camera uploads/CameraUploads.m @@ -173,7 +173,7 @@ - (void)getAssetsForUpload { assetsFetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions]; } - MEGALogInfo(@"Retrieved assets %ld", assetsFetchResult.count); + MEGALogInfo(@"Retrieved assets %tu", assetsFetchResult.count); [assetsFetchResult enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger index, BOOL *stop) { if (asset.mediaType == PHAssetMediaTypeVideo && self.isUploadVideosEnabled && ([asset.creationDate timeIntervalSince1970] > [self.lastUploadVideoDate timeIntervalSince1970])) { @@ -185,7 +185,7 @@ - (void)getAssetsForUpload { } }]; - MEGALogInfo(@"Assets in the operation queue %ld", _assetsOperationQueue.operationCount); + MEGALogInfo(@"Assets in the operation queue %tu", _assetsOperationQueue.operationCount); } #pragma mark - MEGATransferDelegate diff --git a/iMEGA/Camera uploads/MEGAAssetOperation.m b/iMEGA/Camera uploads/MEGAAssetOperation.m index ab8bf03b85..e67105935a 100644 --- a/iMEGA/Camera uploads/MEGAAssetOperation.m +++ b/iMEGA/Camera uploads/MEGAAssetOperation.m @@ -159,7 +159,7 @@ - (void)disableCameraUploadsWithError:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"cameraUploadsWillBeDisabled", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; }); } diff --git a/iMEGA/Camera uploads/Photos.storyboard b/iMEGA/Camera uploads/Photos.storyboard index 719fd38f91..5759c1dcc7 100644 --- a/iMEGA/Camera uploads/Photos.storyboard +++ b/iMEGA/Camera uploads/Photos.storyboard @@ -79,7 +79,7 @@ - + diff --git a/iMEGA/Camera uploads/PhotosViewController.m b/iMEGA/Camera uploads/PhotosViewController.m index c3aa3a7136..6f55828234 100644 --- a/iMEGA/Camera uploads/PhotosViewController.m +++ b/iMEGA/Camera uploads/PhotosViewController.m @@ -549,7 +549,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell cell.nodeHandle = [node handle]; - cell.thumbnailSelectionOverlayView.layer.borderColor = [UIColor.mnz_redMain CGColor]; + cell.thumbnailSelectionOverlayView.layer.borderColor = [UIColor.mnz_green00BFA5 CGColor]; cell.thumbnailSelectionOverlayView.hidden = [self.selectedItemsDictionary objectForKey:[NSNumber numberWithLongLong:node.handle]] == nil; if (node.name.mnz_isVideoPathExtension && node.duration > -1) { diff --git a/iMEGA/Categories/MEGANode+MNZCategory.h b/iMEGA/Categories/MEGANode+MNZCategory.h index 822341b28e..4ee7ca9c7c 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.h +++ b/iMEGA/Categories/MEGANode+MNZCategory.h @@ -1,6 +1,7 @@ @interface MEGANode (MNZCategory) +- (void)navigateToParentAndPresent; - (void)mnz_openNodeInNavigationController:(UINavigationController *)navigationController folderLink:(BOOL)isFolderLink; - (UIViewController *)mnz_viewControllerForNodeInFolderLink:(BOOL)isFolderLink; @@ -18,6 +19,7 @@ - (void)mnz_removeSharing; - (void)mnz_copyToGalleryFromTemporaryPath:(NSString *)path; - (void)mnz_restore; +- (void)mnz_saveToPhotosWithApi:(MEGASdk *)api; #pragma mark - File links diff --git a/iMEGA/Categories/MEGANode+MNZCategory.m b/iMEGA/Categories/MEGANode+MNZCategory.m index 0a413868cb..a37a5bb04e 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Categories/MEGANode+MNZCategory.m @@ -9,27 +9,81 @@ #import "MEGA-Swift.h" #import "Helper.h" -#import "MEGANode.h" #import "MEGAMoveRequestDelegate.h" #import "MEGANodeList+MNZCategory.h" +#import "MEGALinkManager.h" #import "MEGAReachabilityManager.h" #import "MEGARemoveRequestDelegate.h" #import "MEGARenameRequestDelegate.h" #import "MEGAShareRequestDelegate.h" #import "MEGAStore.h" +#import "NSFileManager+MNZCategory.h" #import "NSString+MNZCategory.h" #import "UIApplication+MNZCategory.h" +#import "UITextField+MNZCategory.h" #import "BrowserViewController.h" +#import "CloudDriveViewController.h" #import "MainTabBarController.h" #import "MEGAAVViewController.h" #import "MEGANavigationController.h" -#import "MyAccountHallViewController.h" -#import "PreviewDocumentViewController.h" +#import "MEGAPhotoBrowserViewController.h" #import "MEGAQLPreviewController.h" +#import "PreviewDocumentViewController.h" +#import "SharedItemsViewController.h" @implementation MEGANode (MNZCategory) +- (void)navigateToParentAndPresent { + MainTabBarController *mainTBC = (MainTabBarController *) UIApplication.sharedApplication.delegate.window.rootViewController; + + if ([[MEGASdkManager sharedMEGASdk] accessLevelForNode:self] != MEGAShareTypeAccessOwner) { // Node from inshare + mainTBC.selectedIndex = SHARES; + SharedItemsViewController *sharedItemsVC = mainTBC.childViewControllers[SHARES].childViewControllers.firstObject; + [sharedItemsVC selectSegment:0]; // Incoming + } else { + mainTBC.selectedIndex = CLOUD; + } + + UINavigationController *navigationController = [mainTBC.childViewControllers objectAtIndex:mainTBC.selectedIndex]; + [navigationController popToRootViewControllerAnimated:NO]; + + NSArray *parentTreeArray = self.mnz_parentTreeArray; + for (MEGANode *node in parentTreeArray) { + CloudDriveViewController *cloudDriveVC = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"CloudDriveID"]; + cloudDriveVC.parentNode = node; + [navigationController pushViewController:cloudDriveVC animated:NO]; + } + + switch (self.type) { + case MEGANodeTypeFolder: + case MEGANodeTypeRubbish: { + CloudDriveViewController *cloudDriveVC = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"CloudDriveID"]; + cloudDriveVC.parentNode = self; + [navigationController pushViewController:cloudDriveVC animated:NO]; + break; + } + + case MEGANodeTypeFile: { + if (self.name.mnz_isImagePathExtension || self.name.mnz_isVideoPathExtension) { + MEGANode *parentNode = [[MEGASdkManager sharedMEGASdk] nodeForHandle:self.parentHandle]; + MEGANodeList *nodeList = [[MEGASdkManager sharedMEGASdk] childrenForParent:parentNode]; + NSMutableArray *mediaNodesArray = [nodeList mnz_mediaNodesMutableArrayFromNodeList]; + + MEGAPhotoBrowserViewController *photoBrowserVC = [MEGAPhotoBrowserViewController photoBrowserWithMediaNodes:mediaNodesArray api:[MEGASdkManager sharedMEGASdk] displayMode:DisplayModeCloudDrive presentingNode:self preferredIndex:0]; + + [navigationController presentViewController:photoBrowserVC animated:YES completion:nil]; + } else { + [self mnz_openNodeInNavigationController:navigationController folderLink:NO]; + } + break; + } + + default: + break; + } +} + - (void)mnz_openNodeInNavigationController:(UINavigationController *)navigationController folderLink:(BOOL)isFolderLink { UIViewController *viewController = [self mnz_viewControllerForNodeInFolderLink:isFolderLink]; if (viewController) { @@ -39,8 +93,9 @@ - (void)mnz_openNodeInNavigationController:(UINavigationController *)navigationC - (UIViewController *)mnz_viewControllerForNodeInFolderLink:(BOOL)isFolderLink { MEGASdk *api = isFolderLink ? [MEGASdkManager sharedMEGASdkFolder] : [MEGASdkManager sharedMEGASdk]; + MEGASdk *apiForStreaming = [MEGASdkManager sharedMEGASdk].isLoggedIn ? [MEGASdkManager sharedMEGASdk] : [MEGASdkManager sharedMEGASdkFolder]; - MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:self api:api]; + MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:self]; NSString *previewDocumentPath = nil; if (offlineNodeExist) { @@ -90,9 +145,9 @@ - (UIViewController *)mnz_viewControllerForNodeInFolderLink:(BOOL)isFolderLink { return previewController; } - } else if (self.name.mnz_isMultimediaPathExtension && [api httpServerStart:YES port:4443]) { + } else if (self.name.mnz_isMultimediaPathExtension && [apiForStreaming httpServerStart:YES port:4443]) { if (self.mnz_isPlayable) { - MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithNode:self folderLink:isFolderLink]; + MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithNode:self folderLink:isFolderLink apiForStreaming:apiForStreaming]; return megaAVViewController; } else { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"fileNotSupported", @"Alert title shown when users try to stream an unsupported audio/video file") message:AMLocalizedString(@"message_fileNotSupported", @"Alert message shown when users try to stream an unsupported audio/video file") preferredStyle:UIAlertControllerStyleAlert]; @@ -134,7 +189,7 @@ - (void)mnz_generateThumbnailForVideoAtPath:(NSURL *)path { [[MEGASdkManager sharedMEGASdk] createPreview:tmpImagePath destinatioPath:previewFilePath]; [[MEGASdkManager sharedMEGASdk] setPreviewNode:self sourceFilePath:previewFilePath]; - [[NSFileManager defaultManager] removeItemAtPath:tmpImagePath error:nil]; + [NSFileManager.defaultManager mnz_removeItemAtPath:tmpImagePath]; } #pragma mark - Actions @@ -144,7 +199,7 @@ - (BOOL)mnz_downloadNodeOverwriting:(BOOL)overwrite { } - (BOOL)mnz_downloadNodeOverwriting:(BOOL)overwrite api:(MEGASdk *)api { - MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:self api:api]; + MOOfflineNode *offlineNodeExist = [[MEGAStore shareInstance] offlineNodeWithNode:self]; if (offlineNodeExist) { return YES; } else { @@ -162,6 +217,45 @@ - (BOOL)mnz_downloadNodeOverwriting:(BOOL)overwrite api:(MEGASdk *)api { } } +- (void)mnz_saveToPhotosWithApi:(MEGASdk *)api { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + switch (status) { + case PHAuthorizationStatusAuthorized: { + [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saving to Photos…", @"Text shown when starting the process to save a photo or video to Photos app")]; + NSString *temporaryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:self.base64Handle] stringByAppendingPathComponent:self.name]; + NSString *temporaryFingerprint = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:temporaryPath]; + if ([temporaryFingerprint isEqualToString:self.fingerprint]) { + [self mnz_copyToGalleryFromTemporaryPath:temporaryPath]; + } else if ([MEGAReachabilityManager isReachableHUDIfNot]) { + NSString *downloadsDirectory = [[NSFileManager defaultManager] downloadsDirectory]; + downloadsDirectory = downloadsDirectory.mnz_relativeLocalPath; + NSString *offlineNameString = [[MEGASdkManager sharedMEGASdkFolder] escapeFsIncompatible:self.name]; + NSString *localPath = [downloadsDirectory stringByAppendingPathComponent:offlineNameString]; + [[MEGASdkManager sharedMEGASdk] startDownloadNode:[api authorizeNode:self] localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp]]; + } + break; + } + + case PHAuthorizationStatusRestricted: + case PHAuthorizationStatusDenied: { + UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; + + [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + + [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; + }]]; + + [UIApplication.mnz_visibleViewController presentViewController:permissionsAlertController animated:YES completion:nil]; + break; + } + + default: + break; + } + }]; +} + - (void)mnz_renameNodeInViewController:(UIViewController *)viewController { [self mnz_renameNodeInViewController:viewController completion:nil]; } @@ -171,9 +265,18 @@ - (void)mnz_renameNodeInViewController:(UIViewController *)viewController comple UIAlertController *renameAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") message:AMLocalizedString(@"renameNodeMessage", @"Hint text to suggest that the user have to write the new name for the file or folder") preferredStyle:UIAlertControllerStyleAlert]; [renameAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.delegate = self; textField.text = self.name; [textField addTarget:self action:@selector(renameAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + BOOL shouldReturn = YES; + UIAlertController *renameAlertController = (UIAlertController *)UIApplication.mnz_visibleViewController; + if (renameAlertController) { + UIAlertAction *rightButtonAction = renameAlertController.actions.lastObject; + shouldReturn = rightButtonAction.enabled; + } + + return shouldReturn; + }; }]; [renameAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; @@ -317,10 +420,7 @@ - (void)mnz_fileLinkDownloadFromViewController:(UIViewController *)viewControlle UIViewController *rootVC = UIApplication.sharedApplication.delegate.window.rootViewController; if ([rootVC isKindOfClass:MainTabBarController.class]) { MainTabBarController *mainTBC = (MainTabBarController *)rootVC; - mainTBC.selectedIndex = MYACCOUNT; - MEGANavigationController *navigationController = [mainTBC.childViewControllers objectAtIndex:MYACCOUNT]; - MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; - [myAccountHallVC openOffline]; + [mainTBC showOffline]; } [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", nil)]; @@ -329,11 +429,11 @@ - (void)mnz_fileLinkDownloadFromViewController:(UIViewController *)viewControlle }]; } else { if (isFolderLink) { - [[Helper nodesFromLinkMutableArray] addObject:self]; - [Helper setSelectedOptionOnLink:4]; //Download folder or nodes from link + [MEGALinkManager.nodesFromLinkMutableArray addObject:self]; + MEGALinkManager.selectedOption = LinkOptionDownloadFolderOrNodes; } else { - [Helper setLinkNode:self]; - [Helper setSelectedOptionOnLink:2]; //Download file from link + [MEGALinkManager.nodesFromLinkMutableArray addObject:self]; + MEGALinkManager.selectedOption = LinkOptionDownloadNode; } OnboardingViewController *onboardingVC = [OnboardingViewController new]; @@ -355,17 +455,17 @@ - (void)mnz_fileLinkImportFromViewController:(UIViewController *)viewController MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"BrowserNavigationControllerID"]; BrowserViewController *browserVC = navigationController.viewControllers.firstObject; browserVC.selectedNodesArray = [NSArray arrayWithObject:self]; - [UIApplication.mnz_visibleViewController presentViewController:navigationController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; browserVC.browserAction = isFolderLink ? BrowserActionImportFromFolderLink : BrowserActionImport; }]; } else { if (isFolderLink) { - [[Helper nodesFromLinkMutableArray] addObject:self]; - [Helper setSelectedOptionOnLink:3]; //Import folder or nodes from link + [MEGALinkManager.nodesFromLinkMutableArray addObject:self]; + MEGALinkManager.selectedOption = LinkOptionImportFolderOrNodes; } else { - [Helper setLinkNode:self]; - [Helper setSelectedOptionOnLink:1]; //Import file from link + [MEGALinkManager.nodesFromLinkMutableArray addObject:self]; + MEGALinkManager.selectedOption = LinkOptionImportNode; } OnboardingViewController *onboardingVC = [OnboardingViewController new]; @@ -724,11 +824,9 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang return shouldChangeCharacters; } -- (void)renameAlertTextFieldDidChange:(UITextField *)sender { - - UIAlertController *renameAlertController = (UIAlertController*)UIApplication.mnz_visibleViewController; +- (void)renameAlertTextFieldDidChange:(UITextField *)textField { + UIAlertController *renameAlertController = (UIAlertController *)UIApplication.mnz_visibleViewController; if (renameAlertController) { - UITextField *textField = renameAlertController.textFields.firstObject; UIAlertAction *rightButtonAction = renameAlertController.actions.lastObject; BOOL enableRightButton = NO; @@ -736,13 +834,13 @@ - (void)renameAlertTextFieldDidChange:(UITextField *)sender { NSString *nodeNameString = self.name; if (self.isFile || self.isFolder) { - BOOL containsInvalidChars = [sender.text rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"|*/:<>?\"\\"]].length; + BOOL containsInvalidChars = textField.text.mnz_containsInvalidChars; if ([newName isEqualToString:@""] || [newName isEqualToString:nodeNameString] || newName.mnz_isEmpty || containsInvalidChars) { enableRightButton = NO; } else { enableRightButton = YES; } - sender.textColor = containsInvalidChars ? UIColor.mnz_redMain : UIColor.darkTextColor; + textField.textColor = containsInvalidChars ? UIColor.mnz_redMain : UIColor.darkTextColor; } rightButtonAction.enabled = enableRightButton; @@ -750,8 +848,13 @@ - (void)renameAlertTextFieldDidChange:(UITextField *)sender { } - (void)mnz_copyToGalleryFromTemporaryPath:(NSString *)path { - if (self.name.mnz_isVideoPathExtension && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) { - UISaveVideoAtPathToSavedPhotosAlbum(path, self, @selector(video:didFinishSavingWithError:contextInfo:), nil); + if (self.name.mnz_isVideoPathExtension) { + if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) { + UISaveVideoAtPathToSavedPhotosAlbum(path, self, @selector(video:didFinishSavingWithError:contextInfo:), nil); + } else { + [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"Could not save Item", @"Text shown when an error occurs when trying to save a photo or video to Photos app")]; + MEGALogError(@"The video can be saved to the Camera Roll album"); + } } if (self.name.mnz_isImagePathExtension) { @@ -762,9 +865,12 @@ - (void)mnz_copyToGalleryFromTemporaryPath:(NSString *)path { [assetCreationRequest addResourceWithType:PHAssetResourceTypePhoto fileURL:imageURL options:nil]; } completionHandler:^(BOOL success, NSError * _Nullable nserror) { - [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; + [NSFileManager.defaultManager mnz_removeItemAtPath:path]; if (nserror) { - MEGALogError(@"Add asset to camera roll: %@ (Domain: %@ - Code:%ld)", nserror.localizedDescription, nserror.domain, nserror.code); + [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"Could not save Item", @"Text shown when an error occurs when trying to save a photo or video to Photos app")]; + MEGALogError(@"Add asset to camera roll: %@ (Domain: %@ - Code:%td)", nserror.localizedDescription, nserror.domain, nserror.code); + } else { + [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saved to Photos", @"Text shown when a photo or video is saved to Photos app")]; } }]; } @@ -772,9 +878,11 @@ - (void)mnz_copyToGalleryFromTemporaryPath:(NSString *)path { - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { if (error) { - MEGALogError(@"Save video to Camera roll: %@ (Domain: %@ - Code:%ld)", error.localizedDescription, error.domain, error.code); + [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"Could not save Item", @"Text shown when an error occurs when trying to save a photo or video to Photos app")]; + MEGALogError(@"Save video to Camera roll: %@ (Domain: %@ - Code:%td)", error.localizedDescription, error.domain, error.code); } else { - [[NSFileManager defaultManager] removeItemAtPath:videoPath error:nil]; + [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saved to Photos", @"Text shown when a photo or video is saved to Photos app")]; + [NSFileManager.defaultManager mnz_removeItemAtPath:videoPath]; } } diff --git a/iMEGA/Categories/MEGATransfer+MNZCategory.m b/iMEGA/Categories/MEGATransfer+MNZCategory.m index 48f54ff131..d296b94845 100644 --- a/iMEGA/Categories/MEGATransfer+MNZCategory.m +++ b/iMEGA/Categories/MEGATransfer+MNZCategory.m @@ -8,6 +8,7 @@ #import "MEGANode+MNZCategory.h" #import "MEGASdkManager.h" #import "MEGAReachabilityManager.h" +#import "NSFileManager+MNZCategory.h" #import "NSString+MNZCategory.h" @implementation MEGATransfer (MNZCategory) @@ -43,7 +44,7 @@ - (void)mnz_createThumbnailAndPreview { [[MEGASdkManager sharedMEGASdk] createPreview:imageFilePath destinatioPath:previewFilePath]; if (self.fileName.mnz_isVideoPathExtension) { - [[NSFileManager defaultManager] removeItemAtPath:imageFilePath error:nil]; + [NSFileManager.defaultManager mnz_removeItemAtPath:imageFilePath]; } } @@ -66,8 +67,8 @@ - (void)mnz_renameOrRemoveThumbnailAndPreview { case MEGATransferStateCancelled: case MEGATransferStateFailed: { - [[NSFileManager defaultManager] removeItemAtPath:thumbnailPath error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:previewPath error:nil]; + [NSFileManager.defaultManager mnz_removeItemAtPath:thumbnailPath]; + [NSFileManager.defaultManager mnz_removeItemAtPath:previewPath]; break; } diff --git a/iMEGA/Categories/MEGATransferList+MNZCategory.h b/iMEGA/Categories/MEGATransferList+MNZCategory.h new file mode 100644 index 0000000000..39bed681a6 --- /dev/null +++ b/iMEGA/Categories/MEGATransferList+MNZCategory.h @@ -0,0 +1,6 @@ + +@interface MEGATransferList (MNZCategory) + +- (NSArray *)mnz_transfersArrayFromTranferList; + +@end diff --git a/iMEGA/Categories/MEGATransferList+MNZCategory.m b/iMEGA/Categories/MEGATransferList+MNZCategory.m new file mode 100644 index 0000000000..b7220b68d1 --- /dev/null +++ b/iMEGA/Categories/MEGATransferList+MNZCategory.m @@ -0,0 +1,17 @@ + +#import "MEGATransferList+MNZCategory.h" + +@implementation MEGATransferList (MNZCategory) + +- (NSArray *)mnz_transfersArrayFromTranferList { + NSUInteger transferListCount = self.size.unsignedIntegerValue; + NSMutableArray *transfersMutableArray = [[NSMutableArray alloc] initWithCapacity:transferListCount]; + for (NSUInteger i = 0; i < transferListCount; i++) { + MEGATransfer *transfer = [self transferAtIndex:i]; + [transfersMutableArray addObject:transfer]; + } + + return transfersMutableArray; +} + +@end diff --git a/iMEGA/Categories/MEGAUserAlertList+MNZCategory.h b/iMEGA/Categories/MEGAUserAlertList+MNZCategory.h new file mode 100644 index 0000000000..cf1e5d790d --- /dev/null +++ b/iMEGA/Categories/MEGAUserAlertList+MNZCategory.h @@ -0,0 +1,11 @@ + +NS_ASSUME_NONNULL_BEGIN + +@interface MEGAUserAlertList (MNZCategory) + +@property (nonatomic, readonly) NSArray *mnz_userAlertsArray; +@property (nonatomic, readonly) NSUInteger mnz_unseenCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Categories/MEGAUserAlertList+MNZCategory.m b/iMEGA/Categories/MEGAUserAlertList+MNZCategory.m new file mode 100644 index 0000000000..7faa579e02 --- /dev/null +++ b/iMEGA/Categories/MEGAUserAlertList+MNZCategory.m @@ -0,0 +1,34 @@ + +#import "MEGAUserAlertList+MNZCategory.h" + +#import "MEGAUserAlert.h" + +@implementation MEGAUserAlertList (MNZCategory) + +- (NSArray *)mnz_userAlertsArray { + NSInteger userAlertListCount = self.size; + + NSMutableArray *userAlertsArray = [[NSMutableArray alloc] initWithCapacity:userAlertListCount]; + for (NSUInteger i = 0; i < userAlertListCount; i++) { + MEGAUserAlert *userAlert = [self usertAlertAtIndex:i]; + [userAlertsArray insertObject:userAlert atIndex:0]; + } + + return [userAlertsArray copy]; +} + +- (NSUInteger)mnz_unseenCount { + NSUInteger unseenCount = 0; + + NSInteger userAlertListCount = self.size; + for (NSUInteger i = 0; i < userAlertListCount; i++) { + MEGAUserAlert *userAlert = [self usertAlertAtIndex:i]; + if (!userAlert.isSeen) { + unseenCount++; + } + } + + return unseenCount; +} + +@end diff --git a/iMEGA/Categories/NSAttributedString+MNZCategory.m b/iMEGA/Categories/NSAttributedString+MNZCategory.m index 189b5931ec..8b997f0790 100644 --- a/iMEGA/Categories/NSAttributedString+MNZCategory.m +++ b/iMEGA/Categories/NSAttributedString+MNZCategory.m @@ -17,7 +17,8 @@ + (NSAttributedString *)mnz_attributedStringFromMessage:(NSString *)message cache.countLimit = 1000; }); - NSString *key = [[NSString stringWithFormat:@"%@%@", message, color.description] SHA256]; + NSString *fontKey = [NSString stringWithFormat:@"%@%.2f", font.fontName, font.pointSize]; + NSString *key = [[NSString stringWithFormat:@"%@%@%@", message, color.description, fontKey] SHA256]; NSAttributedString *cachedAttributedString = [cache objectForKey:key]; if (cachedAttributedString) { diff --git a/iMEGA/Categories/NSFileManager+MNZCategory.h b/iMEGA/Categories/NSFileManager+MNZCategory.h index cb80ac74c3..d78452408e 100644 --- a/iMEGA/Categories/NSFileManager+MNZCategory.h +++ b/iMEGA/Categories/NSFileManager+MNZCategory.h @@ -3,7 +3,17 @@ @interface NSFileManager (MNZCategory) +#pragma mark - Paths + - (NSString *)downloadsDirectory; - (NSString *)uploadsDirectory; +#pragma mark - Remove files and folders + +- (void)mnz_removeItemAtPath:(NSString *)path; +- (void)mnz_removeFolderContentsAtPath:(NSString *)folderPath; +- (void)mnz_removeFolderContentsAtPath:(NSString *)folderPath forItemsContaining:(NSString *)filesContaining; +- (void)mnz_removeFolderContentsRecursivelyAtPath:(NSString *)folderPath forItemsContaining:(NSString *)itemsContaining; +- (void)mnz_removeFolderContentsRecursivelyAtPath:(NSString *)folderPath forItemsExtension:(NSString *)itemsExtension; + @end diff --git a/iMEGA/Categories/NSFileManager+MNZCategory.m b/iMEGA/Categories/NSFileManager+MNZCategory.m index 52806a239e..55c08e8e1c 100644 --- a/iMEGA/Categories/NSFileManager+MNZCategory.m +++ b/iMEGA/Categories/NSFileManager+MNZCategory.m @@ -3,6 +3,8 @@ @implementation NSFileManager (MNZCategory) +#pragma mark - Paths + - (NSString *)downloadsDirectory { NSString *downloadsDirectory = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Downloads"]; if (![[NSFileManager defaultManager] fileExistsAtPath:downloadsDirectory]) { @@ -27,4 +29,74 @@ - (NSString *)uploadsDirectory { return uploadsDirectory; } +#pragma mark - Manage files and folders + +- (void)mnz_removeItemAtPath:(NSString *)path { + if (path == nil) { + MEGALogError(@"The path to remove the item is nil."); + return; + } + + NSError *error = nil; + if ([NSFileManager.defaultManager removeItemAtPath:path error:&error]) { + MEGALogInfo(@"Remove item at path succeed:\n- At path: %@", path); + } else { + if ([error.domain isEqualToString:NSCocoaErrorDomain]) { + switch (error.code) { + case NSFileNoSuchFileError: + MEGALogError(@"Remove item operation attempted on non-existent file:\n- At path: %@", path); + break; + + default: + MEGALogError(@"Remove item failed:\n- At path: %@\n- With error: %@", path, error); + break; + } + } + } +} + +- (void)mnz_removeFolderContentsAtPath:(NSString *)folderPath { + NSArray *directoryContentsArray = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil]; + for (NSString *itemName in directoryContentsArray) { + [NSFileManager.defaultManager mnz_removeItemAtPath:[folderPath stringByAppendingPathComponent:itemName]]; + } +} + +- (void)mnz_removeFolderContentsAtPath:(NSString *)folderPath forItemsContaining:(NSString *)itemsContaining { + NSArray *directoryContentsArray = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil]; + for (NSString *itemName in directoryContentsArray) { + if ([itemName.lowercaseString containsString:itemsContaining]) { + [NSFileManager.defaultManager mnz_removeItemAtPath:[folderPath stringByAppendingPathComponent:itemName]]; + } + } +} + +- (void)mnz_removeFolderContentsRecursivelyAtPath:(NSString *)folderPath forItemsContaining:(NSString *)itemsContaining { + NSArray *directoryContentsArray = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil]; + for (NSString *itemName in directoryContentsArray) { + NSDictionary *attributesDictionary = [NSFileManager.defaultManager attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:itemName] error:nil]; + if ([attributesDictionary objectForKey:NSFileType] == NSFileTypeDirectory) { + [NSFileManager.defaultManager mnz_removeFolderContentsRecursivelyAtPath:[folderPath stringByAppendingPathComponent:itemName] forItemsContaining:itemsContaining]; + } else { + if ([itemName.lowercaseString containsString:itemsContaining]) { + [NSFileManager.defaultManager mnz_removeItemAtPath:[folderPath stringByAppendingPathComponent:itemName]]; + } + } + } +} + +- (void)mnz_removeFolderContentsRecursivelyAtPath:(NSString *)folderPath forItemsExtension:(NSString *)itemsExtension { + NSArray *directoryContentsArray = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil]; + for (NSString *itemName in directoryContentsArray) { + NSDictionary *attributesDictionary = [NSFileManager.defaultManager attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:itemName] error:nil]; + if ([attributesDictionary objectForKey:NSFileType] == NSFileTypeDirectory) { + [NSFileManager.defaultManager mnz_removeFolderContentsRecursivelyAtPath:[folderPath stringByAppendingPathComponent:itemName] forItemsExtension:itemsExtension]; + } else { + if ([itemName.pathExtension.lowercaseString isEqualToString:itemsExtension]) { + [NSFileManager.defaultManager mnz_removeItemAtPath:[folderPath stringByAppendingPathComponent:itemName]]; + } + } + } +} + @end diff --git a/iMEGA/Categories/NSString+MNZCategory.h b/iMEGA/Categories/NSString+MNZCategory.h index b337ff2597..00ee0dbd04 100644 --- a/iMEGA/Categories/NSString+MNZCategory.h +++ b/iMEGA/Categories/NSString+MNZCategory.h @@ -16,6 +16,7 @@ typedef NS_ENUM(NSInteger, MEGAChatMessageEndCallReason); - (NSString *)mnz_appDataToSaveInPhotosApp; - (NSString *)mnz_appDataToAttachToChatID:(uint64_t)chatId; - (NSString *)mnz_appDataToSaveCoordinates:(NSString *)coordinates; +- (NSString *)mnz_appDataToLocalIdentifier:(NSString *)localIdentifier; #pragma mark - Utils @@ -33,6 +34,8 @@ typedef NS_ENUM(NSInteger, MEGAChatMessageEndCallReason); - (BOOL)mnz_isEmpty; +- (BOOL)mnz_containsInvalidChars; + - (NSString *)mnz_removeWebclientFormatters; + (NSString *)mnz_stringFromTimeInterval:(NSTimeInterval)interval; @@ -51,6 +54,8 @@ typedef NS_ENUM(NSInteger, MEGAChatMessageEndCallReason); - (NSString *)mnz_relativeLocalPath; ++ (NSString *)mnz_lastGreenStringFromMinutes:(NSInteger)minutes; + #pragma mark - File names and extensions + (NSString *)mnz_fileNameWithDate:(NSDate *)date; diff --git a/iMEGA/Categories/NSString+MNZCategory.m b/iMEGA/Categories/NSString+MNZCategory.m index 55bcd9037c..b978859186 100644 --- a/iMEGA/Categories/NSString+MNZCategory.m +++ b/iMEGA/Categories/NSString+MNZCategory.m @@ -6,6 +6,8 @@ #import #import +#import "NSDate+DateTools.h" + #import "MEGASdkManager.h" static NSString* const A = @"[A]"; @@ -81,7 +83,7 @@ - (BOOL)mnz_isMultimediaPathExtension { #pragma mark - appData - (NSString *)mnz_appDataToSaveCameraUploadsCount:(NSUInteger)operationCount { - return [self stringByAppendingString:[NSString stringWithFormat:@">CU=%ld", operationCount]]; + return [self stringByAppendingString:[NSString stringWithFormat:@">CU=%tu", operationCount]]; } - (NSString *)mnz_appDataToSaveInPhotosApp { @@ -96,6 +98,10 @@ - (NSString *)mnz_appDataToSaveCoordinates:(NSString *)coordinates { return (coordinates ? [self stringByAppendingString:[NSString stringWithFormat:@">setCoordinates=%@", coordinates]] : self); } +- (NSString *)mnz_appDataToLocalIdentifier:(NSString *)localIdentifier { + return (localIdentifier ? [self stringByAppendingString:[NSString stringWithFormat:@">localIdentifier=%@", localIdentifier]] : self); +} + #pragma mark - Utils + (NSString *)mnz_stringWithoutUnitOfComponents:(NSArray *)componentsSeparatedByStringArray { @@ -300,6 +306,10 @@ - (BOOL)mnz_isEmpty { return ![[self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]; } +- (BOOL)mnz_containsInvalidChars { + return [self rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"|*/:<>?\"\\"]].length; +} + - (NSString *)mnz_removeWebclientFormatters { NSString *string; string = [self stringByReplacingOccurrencesOfString:@"[A]" withString:@""]; @@ -346,8 +356,8 @@ + (NSString *)mnz_stringFromCallDuration:(NSInteger)duration { return [NSString stringWithFormat:AMLocalizedString(@"xHours1Minute", nil), (int)hours]; } else { NSString *durationString = AMLocalizedString(@"xHoursxMinutes", nil); - durationString = [durationString stringByReplacingOccurrencesOfString:@"%1$d" withString:[NSString stringWithFormat:@"%lu", hours]]; - durationString = [durationString stringByReplacingOccurrencesOfString:@"%2$d" withString:[NSString stringWithFormat:@"%lu", minutes]]; + durationString = [durationString stringByReplacingOccurrencesOfString:@"%1$d" withString:[NSString stringWithFormat:@"%td", hours]]; + durationString = [durationString stringByReplacingOccurrencesOfString:@"%2$d" withString:[NSString stringWithFormat:@"%td", minutes]]; return durationString; } } @@ -707,6 +717,28 @@ - (NSString *)mnz_relativeLocalPath { return [self stringByReplacingOccurrencesOfString:[NSHomeDirectory() stringByAppendingString:@"/"] withString:@""]; } ++ (NSString *)mnz_lastGreenStringFromMinutes:(NSInteger)minutes { + NSString *lastSeenMessage; + if (minutes < 65535) { + NSDate *dateLastSeen = [NSDate dateWithTimeIntervalSinceNow:-minutes*SECONDS_IN_MINUTE]; + NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init]; + timeFormatter.dateFormat = @"HH:mm"; + timeFormatter.locale = [NSLocale autoupdatingCurrentLocale]; + NSString *timeString = [timeFormatter stringFromDate:dateLastSeen]; + NSString *dateString = [[NSCalendar currentCalendar] isDateInToday:dateLastSeen] ? AMLocalizedString(@"Today", @"") : [dateLastSeen formattedDateWithFormat:@"dd MMM"]; + lastSeenMessage = AMLocalizedString(@"Last seen %s", @"Shown when viewing a 1on1 chat (at least for now), if the user is offline."); + BOOL isRTLLanguage = UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + if (isRTLLanguage) { + lastSeenMessage = [lastSeenMessage stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%@ %@", timeString, dateString]]; + } else { + lastSeenMessage = [lastSeenMessage stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%@ %@", dateString, timeString]]; + } + } else { + lastSeenMessage = AMLocalizedString(@"Last seen a long time ago", @"Text to inform the user the 'Last seen' time of a contact is a long time ago (more than 65535 minutes)"); + } + return lastSeenMessage; +} + #pragma mark - File names and extensions + (NSString *)mnz_fileNameWithDate:(NSDate *)date { diff --git a/iMEGA/Categories/NSURL+MNZCategory.h b/iMEGA/Categories/NSURL+MNZCategory.h index 2d4c35f058..05cc4c83d3 100644 --- a/iMEGA/Categories/NSURL+MNZCategory.h +++ b/iMEGA/Categories/NSURL+MNZCategory.h @@ -1,14 +1,12 @@ -#import - #import "URLType.h" @interface NSURL (MNZCategory) +- (void)mnz_presentSafariViewController; + - (URLType)mnz_type; - (NSString *)mnz_MEGAURL; - (NSString *)mnz_afterSlashesString; -- (void)mnz_showLinkView; - @end diff --git a/iMEGA/Categories/NSURL+MNZCategory.m b/iMEGA/Categories/NSURL+MNZCategory.m index 82b3f1ea53..63dac601d5 100644 --- a/iMEGA/Categories/NSURL+MNZCategory.m +++ b/iMEGA/Categories/NSURL+MNZCategory.m @@ -1,24 +1,49 @@ #import "NSURL+MNZCategory.h" +#import + #import "SVProgressHUD.h" -#import "CustomModalAlertViewController.h" -#import "FileLinkViewController.h" -#import "FolderLinkViewController.h" -#import "MEGAContactLinkQueryRequestDelegate.h" -#import "MEGAGetPublicNodeRequestDelegate.h" -#import "MEGAInviteContactRequestDelegate.h" -#import "MEGANavigationController.h" -#import "MEGANode+MNZCategory.h" -#import "MEGAPhotoBrowserViewController.h" -#import "MEGASdkManager.h" -#import "NSString+MNZCategory.h" +#import "MEGAReachabilityManager.h" #import "UIApplication+MNZCategory.h" -#import "UIImage+GKContact.h" @implementation NSURL (MNZCategory) +- (void)mnz_presentSafariViewController { + if (!([self.scheme.lowercaseString isEqualToString:@"http"] || [self.scheme.lowercaseString isEqualToString:@"https"])) { + if (@available(iOS 10.0, *)) { + [UIApplication.sharedApplication openURL:self options:@{} completionHandler:^(BOOL success) { + if (success) { + MEGALogInfo(@"URL opened on other app"); + } else { + MEGALogInfo(@"URL NOT opened"); + [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; + } + }]; + } else { + if ([UIApplication.sharedApplication openURL:self]) { + MEGALogInfo(@"URL opened on other app"); + } else { + MEGALogInfo(@"URL NOT opened"); + [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; + } + } + return; + } + + if ([MEGAReachabilityManager isReachableHUDIfNot]) { + SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:self]; + if (@available(iOS 10.0, *)) { + safariViewController.preferredControlTintColor = UIColor.mnz_redMain; + } else { + safariViewController.view.tintColor = UIColor.mnz_redMain; + } + + [UIApplication.mnz_visibleViewController presentViewController:safariViewController animated:YES completion:nil]; + } +} + - (URLType)mnz_type { URLType type = URLTypeDefault; @@ -124,6 +149,9 @@ - (NSString *)mnz_afterSlashesString { // http(s)://(www.)mega(.co).nz/ NSArray *components = [self.absoluteString componentsSeparatedByString:@"/"]; afterSlashesString = @""; + if (components.count < 3 || (![components[2] hasSuffix:@"mega.nz"] && ![components[2] isEqualToString:@"mega.co.nz"])) { + return afterSlashesString; + } for (NSUInteger i = 3; i < components.count; i++) { afterSlashesString = [NSString stringWithFormat:@"%@%@/", afterSlashesString, [components objectAtIndex:i]]; } @@ -135,158 +163,4 @@ - (NSString *)mnz_afterSlashesString { return afterSlashesString; } -#pragma mark - Link processing - -- (void)mnz_showLinkView { - switch ([self mnz_type]) { - case URLTypeFileLink: - [self showFileLinkView]; - break; - - case URLTypeFolderLink: - [self showFolderLinkView]; - break; - - case URLTypeContactLink: - [self handleContactLink]; - break; - - default: - break; - } -} - -- (void)showFileLinkView { - NSString *fileLinkURLString = [self mnz_MEGAURL]; - MEGAGetPublicNodeRequestDelegate *delegate = [[MEGAGetPublicNodeRequestDelegate alloc] initWithCompletion:^(MEGARequest *request, MEGAError *error) { - if (error.type) { - [self presentFileLinkViewForLink:fileLinkURLString request:request error:error]; - } else { - MEGANode *node = request.publicNode; - if (node.name.mnz_isImagePathExtension || node.name.mnz_isVideoPathExtension) { - NSString *previewsDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"previewsV3"]; - if (![[NSFileManager defaultManager] fileExistsAtPath:previewsDirectory]) { - NSError *nserror; - if (![[NSFileManager defaultManager] createDirectoryAtPath:previewsDirectory withIntermediateDirectories:NO attributes:nil error:&nserror]) { - MEGALogError(@"Create directory at path failed with error: %@", nserror); - } - } - - MEGAPhotoBrowserViewController *photoBrowserVC = [MEGAPhotoBrowserViewController photoBrowserWithMediaNodes:@[node].mutableCopy api:[MEGASdkManager sharedMEGASdkFolder] displayMode:DisplayModeFileLink presentingNode:node preferredIndex:0]; - photoBrowserVC.publicLink = fileLinkURLString; - - [UIApplication.mnz_visibleViewController presentViewController:photoBrowserVC animated:YES completion:nil]; - } else { - [self presentFileLinkViewForLink:fileLinkURLString request:request error:error]; - } - } - - [SVProgressHUD dismiss]; - }]; - - [SVProgressHUD show]; - [[MEGASdkManager sharedMEGASdk] publicNodeForMegaFileLink:fileLinkURLString delegate:delegate]; -} - - -- (void)presentFileLinkViewForLink:(NSString *)link request:(MEGARequest *)request error:(MEGAError *)error { - MEGANavigationController *fileLinkNavigationController = [[UIStoryboard storyboardWithName:@"Links" bundle:nil] instantiateViewControllerWithIdentifier:@"FileLinkNavigationControllerID"]; - FileLinkViewController *fileLinkVC = fileLinkNavigationController.viewControllers.firstObject; - fileLinkVC.fileLinkString = link; - fileLinkVC.request = request; - fileLinkVC.error = error; - - [UIApplication.mnz_visibleViewController presentViewController:fileLinkNavigationController animated:YES completion:nil]; -} - -- (void)showFolderLinkView { - NSString *folderLinkURLString = [self mnz_MEGAURL]; - MEGANavigationController *folderNavigationController = [[UIStoryboard storyboardWithName:@"Links" bundle:nil] instantiateViewControllerWithIdentifier:@"FolderLinkNavigationControllerID"]; - - FolderLinkViewController *folderlinkVC = folderNavigationController.viewControllers.firstObject; - - [folderlinkVC setIsFolderRootNode:YES]; - [folderlinkVC setFolderLinkString:folderLinkURLString]; - - [UIApplication.mnz_visibleViewController presentViewController:folderNavigationController animated:YES completion:nil]; -} - -- (void)handleContactLink { - NSString *afterSlashesString = [self mnz_afterSlashesString]; - NSRange rangeOfPrefix = [afterSlashesString rangeOfString:@"C!"]; - NSString *contactLinkHandle = [afterSlashesString substringFromIndex:(rangeOfPrefix.location + rangeOfPrefix.length)]; - uint64_t handle = [MEGASdk handleForBase64Handle:contactLinkHandle]; - - MEGAContactLinkQueryRequestDelegate *delegate = [[MEGAContactLinkQueryRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { - NSString *fullName = [NSString stringWithFormat:@"%@ %@", request.name, request.text]; - [self presentInviteModalForEmail:request.email fullName:fullName contactLinkHandle:request.nodeHandle image:request.file]; - } onError:^(MEGAError *error) { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; - }]; - - [[MEGASdkManager sharedMEGASdk] contactLinkQueryWithHandle:handle delegate:delegate]; -} - -- (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullName contactLinkHandle:(uint64_t)contactLinkHandle image:(NSString *)imageOnBase64URLEncoding { - CustomModalAlertViewController *inviteOrDismissModal = [[CustomModalAlertViewController alloc] init]; - inviteOrDismissModal.modalPresentationStyle = UIModalPresentationOverCurrentContext; - - if (imageOnBase64URLEncoding.mnz_isEmpty) { - inviteOrDismissModal.image = [UIImage imageForName:fullName.uppercaseString size:CGSizeMake(128.0f, 128.0f) backgroundColor:[UIColor colorFromHexString:[MEGASdk avatarColorForBase64UserHandle:[MEGASdk base64HandleForUserHandle:contactLinkHandle]]] textColor:[UIColor whiteColor] font:[UIFont mnz_SFUIRegularWithSize:64.0f]]; - } else { - inviteOrDismissModal.roundImage = YES; - NSData *imageData = [[NSData alloc] initWithBase64EncodedString:[NSString mnz_base64FromBase64URLEncoding:imageOnBase64URLEncoding] options:NSDataBase64DecodingIgnoreUnknownCharacters]; - inviteOrDismissModal.image = [UIImage imageWithData:imageData]; - } - - inviteOrDismissModal.viewTitle = fullName; - - __weak UIViewController *weakVisibleVC = [UIApplication mnz_visibleViewController]; - __weak CustomModalAlertViewController *weakInviteOrDismissModal = inviteOrDismissModal; - void (^completion)(void) = ^{ - MEGAInviteContactRequestDelegate *delegate = [[MEGAInviteContactRequestDelegate alloc] initWithNumberOfRequests:1 presentSuccessOver:weakVisibleVC completion:nil]; - [[MEGASdkManager sharedMEGASdk] inviteContactWithEmail:email message:@"" action:MEGAInviteActionAdd handle:contactLinkHandle delegate:delegate]; - [weakInviteOrDismissModal dismissViewControllerAnimated:YES completion:nil]; - }; - - void (^onDismiss)(void) = ^{ - [weakInviteOrDismissModal dismissViewControllerAnimated:YES completion:nil]; - }; - - MEGAUser *user = [[MEGASdkManager sharedMEGASdk] contactForEmail:email]; - if (user && user.visibility == MEGAUserVisibilityVisible) { - inviteOrDismissModal.detail = [AMLocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:email]; - inviteOrDismissModal.action = AMLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); - inviteOrDismissModal.completion = onDismiss; - } else { - BOOL isInOutgoingContactRequest = NO; - MEGAContactRequestList *outgoingContactRequestList = [[MEGASdkManager sharedMEGASdk] outgoingContactRequests]; - for (NSInteger i = 0; i < outgoingContactRequestList.size.integerValue; i++) { - MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; - if ([email isEqualToString:contactRequest.targetEmail]) { - isInOutgoingContactRequest = YES; - break; - } - } - if (isInOutgoingContactRequest) { - inviteOrDismissModal.image = [UIImage imageNamed:@"inviteSent"]; - inviteOrDismissModal.viewTitle = AMLocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); - NSString *detailText = AMLocalizedString(@"theUserHasBeenInvited", @"Success message shown when a contact has been invited"); - detailText = [detailText stringByReplacingOccurrencesOfString:@"[X]" withString:email]; - inviteOrDismissModal.detail = detailText; - inviteOrDismissModal.boldInDetail = email; - inviteOrDismissModal.action = AMLocalizedString(@"close", nil); - inviteOrDismissModal.completion = onDismiss; - } else { - inviteOrDismissModal.detail = email; - inviteOrDismissModal.action = AMLocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA."); - inviteOrDismissModal.dismiss = AMLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); - inviteOrDismissModal.completion = completion; - inviteOrDismissModal.onDismiss = onDismiss; - } - } - - [[UIApplication mnz_visibleViewController] presentViewController:inviteOrDismissModal animated:YES completion:nil]; -} - @end diff --git a/iMEGA/Categories/UIApplication+MNZCategory.h b/iMEGA/Categories/UIApplication+MNZCategory.h index ad3813614b..c992a300dc 100644 --- a/iMEGA/Categories/UIApplication+MNZCategory.h +++ b/iMEGA/Categories/UIApplication+MNZCategory.h @@ -3,6 +3,10 @@ @interface UIApplication (MNZCategory) +/* The view controller used to present other view controllers */ ++ (UIViewController *)mnz_presentingViewController; + +/* The visible view controller */ + (UIViewController *)mnz_visibleViewController; @end diff --git a/iMEGA/Categories/UIApplication+MNZCategory.m b/iMEGA/Categories/UIApplication+MNZCategory.m index afa9affcbb..24d9d973a8 100644 --- a/iMEGA/Categories/UIApplication+MNZCategory.m +++ b/iMEGA/Categories/UIApplication+MNZCategory.m @@ -3,7 +3,7 @@ @implementation UIApplication (MNZCategory) -+ (UIViewController *)mnz_visibleViewController { ++ (UIViewController *)mnz_presentingViewController { UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; while (rootViewController.presentedViewController) { @@ -13,4 +13,31 @@ + (UIViewController *)mnz_visibleViewController { return rootViewController; } ++ (UIViewController *)mnz_visibleViewController { + UIViewController *rootViewController = UIApplication.mnz_presentingViewController; + + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *navigationController = (UINavigationController *)rootViewController; + UIViewController *lastViewController = [[navigationController viewControllers] lastObject]; + + return lastViewController; + } + + if ([rootViewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tabBarController = (UITabBarController *)rootViewController; + UIViewController *selectedViewController = tabBarController.selectedViewController; + + if ([selectedViewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *navigationController = (UINavigationController *)selectedViewController; + UIViewController *lastViewController = [[navigationController viewControllers] lastObject]; + + return lastViewController; + } + + return selectedViewController; + } + + return rootViewController; +} + @end diff --git a/iMEGA/Categories/UIColor+MNZCategory.h b/iMEGA/Categories/UIColor+MNZCategory.h index e1a20aedb3..4bce485ec0 100644 --- a/iMEGA/Categories/UIColor+MNZCategory.h +++ b/iMEGA/Categories/UIColor+MNZCategory.h @@ -26,12 +26,14 @@ typedef NS_ENUM (NSInteger, MEGAChatStatus); + (UIColor *)mnz_grayD8D8D8; + (UIColor *)mnz_grayE3E3E3; + (UIColor *)mnz_grayEEEEEE; ++ (UIColor *)mnz_grayFAFAFA; + (UIColor *)mnz_grayFCFCFC; + (UIColor *)mnz_grayF7F7F7; + (UIColor *)mnz_grayF9F9F9; #pragma mark - Green ++ (UIColor *)mnz_green00897B; + (UIColor *)mnz_green00BFA5; + (UIColor *)mnz_green13E03C; + (UIColor *)mnz_green31B500; @@ -44,6 +46,7 @@ typedef NS_ENUM (NSInteger, MEGAChatStatus); #pragma mark - Red + (UIColor *)mnz_redMain; ++ (UIColor *)mnz_redError; + (UIColor *)mnz_redProI; + (UIColor *)mnz_redProII; + (UIColor *)mnz_redProIII; diff --git a/iMEGA/Categories/UIColor+MNZCategory.m b/iMEGA/Categories/UIColor+MNZCategory.m index 7e5fe54915..fabd22560b 100644 --- a/iMEGA/Categories/UIColor+MNZCategory.m +++ b/iMEGA/Categories/UIColor+MNZCategory.m @@ -63,6 +63,10 @@ + (UIColor *)mnz_grayEEEEEE { return [UIColor colorWithRed:238.0/255.0 green:238.0/255.0 blue:238.0/255.0 alpha:1.0]; } ++ (UIColor *)mnz_grayFAFAFA { + return [UIColor colorWithRed:250.0/255.0 green:250.0/255.0 blue:250.0/255.0 alpha:1.0]; +} + + (UIColor *)mnz_grayFCFCFC { return [UIColor colorWithRed:252.0/255.0 green:252.0/255.0 blue:252.0/255.0 alpha:1.0]; } @@ -77,6 +81,10 @@ + (UIColor *)mnz_grayF9F9F9 { #pragma mark - Green ++ (UIColor *)mnz_green00897B { + return [UIColor colorWithRed:0.0f green:0.54 blue:0.48 alpha:1.0f]; +} + + (UIColor *)mnz_green00BFA5 { return [UIColor colorWithRed:0.0f green:0.75 blue:0.65 alpha:1.0f]; } @@ -106,6 +114,10 @@ + (UIColor *)mnz_redMain { return [UIColor mnz_redF30C14]; } ++ (UIColor *)mnz_redError { + return [UIColor mnz_redD90007]; +} + + (UIColor *)mnz_redProI { return [UIColor mnz_redE13339]; } diff --git a/iMEGA/Categories/UIFont+MNZCategory.h b/iMEGA/Categories/UIFont+MNZCategory.h index e93fc373bb..10f539ad04 100644 --- a/iMEGA/Categories/UIFont+MNZCategory.h +++ b/iMEGA/Categories/UIFont+MNZCategory.h @@ -11,4 +11,24 @@ + (UIFont *)mnz_defaultFontForPureEmojiStringWithEmojis:(NSUInteger)emojiCount; + + +/** + Returns a system font object that is the same size as the receiver but which has the specified weight instead. + + @param weight UIFontWeight + @return a system font of the specified weight + */ +- (UIFont *)fontWithWeight:(UIFontWeight)weight; + +/** + @return Returns a font object that is the same as the receiver but which has bold style. + */ +- (UIFont *)bold; + +/** + @return Returns a font object that is the same as the receiver but which has italic style. + */ +- (UIFont *)italic; + @end diff --git a/iMEGA/Categories/UIFont+MNZCategory.m b/iMEGA/Categories/UIFont+MNZCategory.m index 949253933e..78af432908 100644 --- a/iMEGA/Categories/UIFont+MNZCategory.m +++ b/iMEGA/Categories/UIFont+MNZCategory.m @@ -75,4 +75,16 @@ + (UIFont *)mnz_defaultFontForPureEmojiStringWithEmojis:(NSUInteger)emojiCount { return [UIFont mnz_SFUIRegularWithSize:size]; } +- (UIFont *)bold { + return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:0]; +} + +- (UIFont *)italic { + return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:0]; +} + +- (UIFont *)fontWithWeight:(UIFontWeight)weight { + return [UIFont systemFontOfSize:self.pointSize weight:weight]; +} + @end diff --git a/iMEGA/Categories/UITextField+MNZCategory.h b/iMEGA/Categories/UITextField+MNZCategory.h new file mode 100644 index 0000000000..0508b1df82 --- /dev/null +++ b/iMEGA/Categories/UITextField+MNZCategory.h @@ -0,0 +1,8 @@ + +#import + +@interface UITextField (MNZCategory) + +@property (nonatomic, copy) BOOL (^shouldReturnCompletion)(UITextField *textField); + +@end diff --git a/iMEGA/Categories/UITextField+MNZCategory.m b/iMEGA/Categories/UITextField+MNZCategory.m new file mode 100644 index 0000000000..d13b0dac7f --- /dev/null +++ b/iMEGA/Categories/UITextField+MNZCategory.m @@ -0,0 +1,37 @@ + +#import "UITextField+MNZCategory.h" + +#import + +@implementation UITextField (MNZCategory) + +- (BOOL (^)(UITextField *))shouldReturnCompletion { + return objc_getAssociatedObject(self, @selector(shouldReturnCompletion)); +} + +- (void)setShouldReturnCompletion:(BOOL (^)(UITextField *))newShouldReturnCompletion { + [self setDelegate]; + + objc_setAssociatedObject(self, @selector(shouldReturnCompletion), newShouldReturnCompletion, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +#pragma mark - Private + +- (void)setDelegate { + if (self.delegate != (id)self.class) { + objc_setAssociatedObject(self, _cmd, self.delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + self.delegate = (id)self.class; + } +} + +#pragma mark - UITextFieldDelegate + ++ (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (textField.shouldReturnCompletion) { + return textField.shouldReturnCompletion(textField); + } + + return YES; +} + +@end diff --git a/iMEGA/Chat/CallViewController.m b/iMEGA/Chat/CallViewController.m index 9b122a7170..ce5ae70361 100644 --- a/iMEGA/Chat/CallViewController.m +++ b/iMEGA/Chat/CallViewController.m @@ -255,7 +255,7 @@ - (void)showOrHideControls { - (void)enablePasscodeIfNeeded { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"presentPasscodeLater"] && [LTHPasscodeViewController doesPasscodeExist]) { - [[LTHPasscodeViewController sharedUser] showLockScreenOver:UIApplication.mnz_visibleViewController.view + [[LTHPasscodeViewController sharedUser] showLockScreenOver:UIApplication.mnz_presentingViewController.view withAnimation:YES withLogout:NO andLogoutTitle:nil]; diff --git a/iMEGA/Chat/Chat.storyboard b/iMEGA/Chat/Chat.storyboard index 6860864699..c9460a8381 100644 --- a/iMEGA/Chat/Chat.storyboard +++ b/iMEGA/Chat/Chat.storyboard @@ -1,11 +1,11 @@ - + - + @@ -26,106 +26,111 @@ - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + + + + @@ -137,60 +142,61 @@ + - - + + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + @@ -235,7 +241,7 @@ - + @@ -763,7 +769,7 @@ - + diff --git a/iMEGA/Chat/ChatRoomCell.h b/iMEGA/Chat/ChatRoomCell.h index c264bf4676..b5cce22769 100644 --- a/iMEGA/Chat/ChatRoomCell.h +++ b/iMEGA/Chat/ChatRoomCell.h @@ -9,5 +9,6 @@ @property (weak, nonatomic) IBOutlet UIView *onlineStatusView; @property (weak, nonatomic) IBOutlet UILabel *unreadCount; @property (weak, nonatomic) IBOutlet UIView *unreadView; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *unreadCountLabelHorizontalMarginConstraint; @end diff --git a/iMEGA/Chat/ChatRoomCell.m b/iMEGA/Chat/ChatRoomCell.m index 689c0f95d3..09a78b9d25 100644 --- a/iMEGA/Chat/ChatRoomCell.m +++ b/iMEGA/Chat/ChatRoomCell.m @@ -2,6 +2,17 @@ @implementation ChatRoomCell +- (void)awakeFromNib { + [super awakeFromNib]; + + if (@available(iOS 10.0, *)) { + self.chatTitle.adjustsFontForContentSizeCategory = YES; + self.chatLastMessage.adjustsFontForContentSizeCategory = YES; + self.chatLastTime.adjustsFontForContentSizeCategory = YES; + self.unreadCount.adjustsFontForContentSizeCategory = YES; + } +} + - (void)setSelected:(BOOL)selected animated:(BOOL)animated { UIColor *color = self.onlineStatusView.backgroundColor; [super setSelected:selected animated:animated]; diff --git a/iMEGA/Chat/ChatRoomsViewController.m b/iMEGA/Chat/ChatRoomsViewController.m index 5c033200e2..16a0cad273 100644 --- a/iMEGA/Chat/ChatRoomsViewController.m +++ b/iMEGA/Chat/ChatRoomsViewController.m @@ -146,13 +146,13 @@ - (void)viewWillDisappear:(BOOL)animated { [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; [[MEGASdkManager sharedMEGAChatSdk] removeChatDelegate:self]; - - [self.chatListItemArray removeAllObjects]; - [self.chatIdIndexPathDictionary removeAllObjects]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; + + [self.chatListItemArray removeAllObjects]; + [self.chatIdIndexPathDictionary removeAllObjects]; [self.tableView reloadData]; } @@ -228,7 +228,7 @@ - (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView { } } - NSDictionary *attributes = @{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:14.0f], NSForegroundColorAttributeName:[UIColor mnz_gray777777]}; + NSDictionary *attributes = @{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote], NSForegroundColorAttributeName:[UIColor mnz_gray777777]}; return [[NSAttributedString alloc] initWithString:text attributes:attributes]; } @@ -322,7 +322,19 @@ - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button - (void)openChatRoomWithID:(uint64_t)chatID { NSArray *viewControllers = self.navigationController.viewControllers; if (viewControllers.count > 1) { - [self.navigationController popToRootViewControllerAnimated:NO]; + UIViewController *currentVC = self.navigationController.viewControllers[1]; + if ([currentVC isKindOfClass:MessagesViewController.class]) { + MessagesViewController *currentMessagesVC = (MessagesViewController *)currentVC; + if (currentMessagesVC.chatRoom.chatId == chatID) { + if (viewControllers.count != 2) { + [self.navigationController popToViewController:currentMessagesVC animated:YES]; + } + return; + } else { + [[MEGASdkManager sharedMEGAChatSdk] closeChatRoom:currentMessagesVC.chatRoom.chatId delegate:currentMessagesVC]; + [self.navigationController popToRootViewControllerAnimated:NO]; + } + } } MessagesViewController *messagesVC = [[MessagesViewController alloc] init]; @@ -411,30 +423,28 @@ - (void)updateChatIdIndexPathDictionary { } - (void)updateCell:(ChatRoomCell *)cell forUnreadCountChange:(NSInteger)unreadCount { + cell.chatTitle.font = [[UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] fontWithWeight:UIFontWeightMedium]; + cell.chatTitle.textColor = [UIColor mnz_black333333]; + if (unreadCount != 0) { - if (cell.unreadView.hidden) { - cell.chatTitle.font = [UIFont mnz_SFUIMediumWithSize:15.0f]; - cell.chatTitle.textColor = [UIColor mnz_black333333]; - cell.chatLastMessage.font = [UIFont mnz_SFUIMediumWithSize:12.0f]; - cell.chatLastMessage.textColor = [UIColor mnz_black333333]; - cell.chatLastTime.font = [UIFont mnz_SFUIMediumWithSize:10.0f]; - cell.chatLastTime.textColor = [UIColor mnz_black333333]; - - cell.unreadView.hidden = NO; - cell.unreadView.clipsToBounds = YES; - } + cell.chatLastMessage.font = [[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1] fontWithWeight:UIFontWeightMedium]; + cell.chatLastMessage.textColor = [UIColor mnz_black333333]; + + cell.chatLastTime.font = [[UIFont preferredFontForTextStyle:UIFontTextStyleCaption2] fontWithWeight:UIFontWeightMedium]; + cell.chatLastTime.textColor = [UIColor mnz_black333333]; + + cell.unreadView.hidden = NO; + cell.unreadView.clipsToBounds = YES; if (unreadCount > 0) { - cell.unreadCount.text = [NSString stringWithFormat:@"%ld", unreadCount]; + cell.unreadCount.text = [NSString stringWithFormat:@"%td", unreadCount]; } else { - cell.unreadCount.text = [NSString stringWithFormat:@"%ld+", -unreadCount]; + cell.unreadCount.text = [NSString stringWithFormat:@"%td+", -unreadCount]; } } else { - cell.chatTitle.font = [UIFont mnz_SFUIMediumWithSize:15.0f]; - cell.chatTitle.textColor = UIColor.mnz_black333333; - cell.chatLastMessage.font = [UIFont mnz_SFUIRegularWithSize:12.0f]; + cell.chatLastMessage.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; cell.chatLastMessage.textColor = [UIColor mnz_gray666666]; - cell.chatLastTime.font = [UIFont mnz_SFUIRegularWithSize:10.0f]; + cell.chatLastTime.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2]; cell.chatLastTime.textColor = [UIColor mnz_gray666666]; cell.unreadView.hidden = YES; @@ -466,7 +476,7 @@ - (void)updateCell:(ChatRoomCell *)cell forChatListItem:(MEGAChatListItem *)item lastMessageString = [attachedFileString stringByReplacingOccurrencesOfString:@"%s" withString:lastMessageString]; } else { lastMessageString = AMLocalizedString(@"attachedXFiles", @"A summary message when a user has attached many files at once into the chat. Please keep %s as it will be replaced at runtime with the number of files."); - lastMessageString = [lastMessageString stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%lu", componentsArray.count]]; + lastMessageString = [lastMessageString stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%tu", componentsArray.count]]; } cell.chatLastMessage.text = senderString ? [NSString stringWithFormat:@"%@: %@",senderString, lastMessageString] : lastMessageString; break; @@ -484,7 +494,7 @@ - (void)updateCell:(ChatRoomCell *)cell forChatListItem:(MEGAChatListItem *)item lastMessageString = [sentContactString stringByReplacingOccurrencesOfString:@"%s" withString:lastMessageString]; } else { lastMessageString = AMLocalizedString(@"sentXContacts", @"A summary message when a user sent the information of %s number of contacts at once. Please keep %s as it will be replaced at runtime with the number of contacts sent."); - lastMessageString = [lastMessageString stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%lu", componentsArray.count]]; + lastMessageString = [lastMessageString stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%tu", componentsArray.count]]; } cell.chatLastMessage.text = senderString ? [NSString stringWithFormat:@"%@: %@",senderString, lastMessageString] : lastMessageString; break; @@ -786,16 +796,8 @@ - (UITableViewCell *)archivedChatRoomCellForIndexPath:(NSIndexPath *)indexPath { cell.unreadView.hidden = NO; cell.unreadView.backgroundColor = UIColor.mnz_gray777777; cell.unreadView.layer.cornerRadius = 4; - NSArray *constraints = cell.unreadView.constraints; - for (NSLayoutConstraint *constraint in constraints) { - if (constraint.firstAttribute == NSLayoutAttributeHeight) { - constraint.constant = 20; - } - if (constraint.firstAttribute == NSLayoutAttributeWidth) { - constraint.constant = 14; - } - } cell.unreadCount.text = AMLocalizedString(@"archived", @"Title of flag of archived chats.").uppercaseString; + cell.unreadCountLabelHorizontalMarginConstraint.constant = 7; return cell; } @@ -910,18 +912,6 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } } -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (self.isArchivedChatsRowVisible) { - if (indexPath.section == 0) { - return 44; - } else { - return 60; - } - } else { - return 60; - } -} - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { switch (self.chatRoomsType) { case ChatRoomsTypeDefault: { diff --git a/iMEGA/Chat/GroupChatDetailsViewController.m b/iMEGA/Chat/GroupChatDetailsViewController.m index a1f93f6bb2..b415279a0d 100644 --- a/iMEGA/Chat/GroupChatDetailsViewController.m +++ b/iMEGA/Chat/GroupChatDetailsViewController.m @@ -5,6 +5,7 @@ #import "MEGAReachabilityManager.h" #import "UIAlertAction+MNZCategory.h" #import "UIImageView+MNZCategory.h" +#import "UITextField+MNZCategory.h" #import "ChatRoomsViewController.h" #import "ContactsViewController.h" @@ -16,7 +17,7 @@ #import "MEGAGlobalDelegate.h" #import "MEGAArchiveChatRequestDelegate.h" -@interface GroupChatDetailsViewController () +@interface GroupChatDetailsViewController () @property (weak, nonatomic) IBOutlet UIImageView *avatarImageView; @property (weak, nonatomic) IBOutlet UILabel *nameLabel; @@ -30,6 +31,7 @@ @interface GroupChatDetailsViewController () *indexPathsMutableDictionary; @property (nonatomic, assign) BOOL openChatRoom; @@ -64,6 +66,7 @@ - (void)viewWillAppear:(BOOL)animated { self.openChatRoom = YES; } [[MEGASdkManager sharedMEGASdk] addMEGAGlobalDelegate:self]; + [[MEGASdkManager sharedMEGAChatSdk] addChatDelegate:self]; [self setParticipants]; } @@ -76,6 +79,7 @@ - (void)viewWillDisappear:(BOOL)animated { [[MEGASdkManager sharedMEGAChatSdk] removeChatRoomDelegate:self.chatRoom.chatId delegate:self]; } [[MEGASdkManager sharedMEGASdk] removeMEGAGlobalDelegate:self]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatDelegate:self]; } - (BOOL)hidesBottomBarWhenPushed { @@ -95,12 +99,13 @@ - (void)setParticipants { uint64_t myHandle = [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]; [self.participantsMutableArray addObject:[NSNumber numberWithUnsignedLongLong:myHandle]]; + + self.indexPathsMutableDictionary = [[NSMutableDictionary alloc] initWithCapacity:self.participantsMutableArray.count]; } -- (void)alertTextFieldDidChange:(UITextField *)sender { +- (void)alertTextFieldDidChange:(UITextField *)textField { UIAlertController *alertController = (UIAlertController *)self.presentedViewController; if (alertController) { - UITextField *textField = alertController.textFields.firstObject; UIAlertAction *rightButtonAction = alertController.actions.lastObject; BOOL enableRightButton = NO; if ((textField.text.length > 0) && ![textField.text isEqualToString:self.chatRoom.title] && ![[textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@""] && ([textField.text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 31)) { @@ -308,6 +313,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N NSInteger index = (self.chatRoom.ownPrivilege == MEGAChatRoomPrivilegeModerator) ? (indexPath.row - 1) : indexPath.row; uint64_t handle = [[self.participantsMutableArray objectAtIndex:index] unsignedLongLongValue]; + NSString *base64Handle = [MEGASdk base64HandleForUserHandle:handle]; + + [self.indexPathsMutableDictionary setObject:indexPath forKey:base64Handle]; + NSString *peerFullname; NSString *peerEmail; MEGAChatRoomPrivilege privilege; @@ -427,6 +436,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [renameGroupAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.text = self.chatRoom.title; [textField addTarget:self action:@selector(alertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + return ((textField.text.length > 0) && ![textField.text isEqualToString:self.chatRoom.title] && ![[textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@""] && ([textField.text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 31)); + }; }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]; @@ -597,6 +609,23 @@ - (void)onChatRoomUpdate:(MEGAChatSdk *)api chat:(MEGAChatRoom *)chat { } } +#pragma mark - MEGAChatDelegate + +- (void)onChatOnlineStatusUpdate:(MEGAChatSdk *)api userHandle:(uint64_t)userHandle status:(MEGAChatStatus)onlineStatus inProgress:(BOOL)inProgress { + if (inProgress) { + return; + } + + if (userHandle != api.myUserHandle) { + NSString *base64Handle = [MEGASdk base64HandleForUserHandle:userHandle]; + NSIndexPath *indexPath = [self.indexPathsMutableDictionary objectForKey:base64Handle]; + if ([self.tableView.indexPathsForVisibleRows containsObject:indexPath]) { + GroupChatDetailsViewTableViewCell *cell = (GroupChatDetailsViewTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; + cell.onlineStatusView.backgroundColor = [UIColor mnz_colorForStatusChange:onlineStatus]; + } + } +} + #pragma mark - MEGAGlobalDelegate - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList { diff --git a/iMEGA/Chat/MEGACallManager.m b/iMEGA/Chat/MEGACallManager.m index e5c07411af..0e11c18124 100644 --- a/iMEGA/Chat/MEGACallManager.m +++ b/iMEGA/Chat/MEGACallManager.m @@ -45,12 +45,17 @@ - (void)endCall:(MEGAChatCall *)call { uuid = [keysArray objectAtIndex:0]; } } + if (uuid) { MEGALogDebug(@"[CallKit] End call %@, uuid: %@", call, uuid); CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid]; CXTransaction *transaction = [[CXTransaction alloc] init]; [transaction addAction:endCallAction]; [self requestTransaction:transaction]; + } else { + MEGALogDebug(@"[CallKit] Call %@ not found in the calls dictionary. Hang the call", call); + [self printAllCalls]; + [[MEGASdkManager sharedMEGAChatSdk] hangChatCall:call.chatId]; } } diff --git a/iMEGA/Chat/MEGAProviderDelegate.m b/iMEGA/Chat/MEGAProviderDelegate.m index 8c1d46f9da..41b3509141 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.m +++ b/iMEGA/Chat/MEGAProviderDelegate.m @@ -177,12 +177,12 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct callVC.megaCallManager = self.megaCallManager; callVC.call = call; - if ([UIApplication.mnz_visibleViewController isKindOfClass:CallViewController.class]) { - [UIApplication.mnz_visibleViewController dismissViewControllerAnimated:YES completion:^{ - [UIApplication.mnz_visibleViewController presentViewController:callVC animated:YES completion:nil]; + if ([UIApplication.mnz_presentingViewController isKindOfClass:CallViewController.class]) { + [UIApplication.mnz_presentingViewController dismissViewControllerAnimated:YES completion:^{ + [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; }]; } else { - [UIApplication.mnz_visibleViewController presentViewController:callVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } [action fulfill]; [self disablePasscodeIfNeeded]; diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index fdc458ef21..7ea9d0bffa 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -6,6 +6,7 @@ #import #import "SVProgressHUD.h" #import "UIImage+GKContact.h" +#import "NSDate+DateTools.h" #import "Helper.h" #import "DevicePermissionsHelper.h" @@ -18,6 +19,7 @@ #import "MEGAMessagesTypingIndicatorFoorterView.h" #import "MEGANode+MNZCategory.h" #import "MEGANodeList+MNZCategory.h" +#import "MEGALinkManager.h" #import "MEGAOpenMessageHeaderView.h" #import "MEGAProcessAsset.h" #import "MEGAReachabilityManager.h" @@ -27,8 +29,8 @@ #import "MEGATransfer+MNZCategory.h" #import "NSAttributedString+MNZCategory.h" #import "NSString+MNZCategory.h" -#import "NSURL+MNZCategory.h" #import "UIImage+MNZCategory.h" +#import "UIApplication+MNZCategory.h" #import "BrowserViewController.h" #import "CallViewController.h" @@ -43,18 +45,18 @@ #import "MEGANavigationController.h" #import "SendToViewController.h" -const CGFloat kGroupChatCellLabelHeight = 35.0f; -const CGFloat k1on1CellLabelHeight = 28.0f; +const CGFloat kGroupChatCellLabelHeightBuffer = 12.0f; +const CGFloat k1on1CellLabelHeightBuffer = 5.0f; const CGFloat kAvatarImageDiameter = 24.0f; const NSUInteger kMaxMessagesToLoad = 256; -@interface MessagesViewController () +@interface MessagesViewController () @property (nonatomic, strong) MEGAOpenMessageHeaderView *openMessageHeaderView; @property (nonatomic, strong) MEGAMessagesTypingIndicatorFoorterView *footerView; -@property (nonatomic, strong) NSMutableArray *messages; +@property (nonatomic, strong) NSMutableArray *messages; @property (strong, nonatomic) JSQMessagesBubbleImage *outgoingBubbleImageData; @property (strong, nonatomic) JSQMessagesBubbleImage *incomingBubbleImageData; @@ -63,6 +65,7 @@ @interface MessagesViewController () *whoIsTypingMutableArray; @@ -106,6 +109,11 @@ @interface MessagesViewController () *selectedMessages; +@property UIView *navigationView; +@property UILabel *navigationTitleLabel; +@property UILabel *navigationSubtitleLabel; +@property UIView *navigationStatusView; + @end @implementation MessagesViewController @@ -116,7 +124,6 @@ - (void)viewDidLoad { [super viewDidLoad]; _messages = [[NSMutableArray alloc] init]; - self.unreadMessages = self.chatRoom.unreadCount; if ([[MEGASdkManager sharedMEGAChatSdk] openChatRoom:self.chatRoom.chatId delegate:self]) { MEGALogDebug(@"Chat room opened: %@", self.chatRoom); @@ -131,86 +138,14 @@ - (void)viewDidLoad { MEGALogError(@"The delegate is NULL or the chatroom is not found"); } - self.inputToolbar.contentView.textView.jsq_pasteDelegate = self; - - UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideInputToolbar)]; - tapGesture.cancelsTouchesInView = NO; - [self.collectionView addGestureRecognizer:tapGesture]; - - [self customiseCollectionViewLayout]; - - [self.collectionView registerNib:[MEGAOpenMessageHeaderView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[MEGAOpenMessageHeaderView headerReuseIdentifier]]; - - [self.collectionView registerNib:[MEGAMessagesTypingIndicatorFoorterView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[MEGAMessagesTypingIndicatorFoorterView footerReuseIdentifier]]; - - //Set up message accessory button delegate and configuration - self.collectionView.accessoryDelegate = self; - - self.showLoadEarlierMessagesHeader = YES; - - //Register custom menu actions for cells. - [JSQMessagesCollectionViewCell registerMenuAction:@selector(edit:message:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(forward:message:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(import:message:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(download:message:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(addContact:message:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(revoke:message:indexPath:)]; - [JSQMessagesCollectionViewCell registerMenuAction:@selector(removeRichPreview:message:indexPath:)]; - + [self setupCollectionView]; [self setupMenuController:[UIMenuController sharedMenuController]]; - - //Allow cells to be deleted - [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; - - JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:[UIImage imageNamed:@"bubble_tailless"] capInsets:UIEdgeInsetsZero layoutDirection:[UIApplication sharedApplication].userInterfaceLayoutDirection]; - - self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor mnz_green00BFA5]]; - self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor mnz_grayE2EAEA]]; - - self.collectionView.backgroundColor = [UIColor whiteColor]; - [self customToolbarContentView]; + self.showLoadEarlierMessagesHeader = YES; self.areAllMessagesSeen = NO; - - UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(popViewController)]; - - _unreadLabel = [[UILabel alloc] initWithFrame:CGRectMake(25, 6, 30, 30)]; - self.unreadLabel.font = [UIFont mnz_SFUIMediumWithSize:12.0f]; - self.unreadLabel.textColor = [UIColor whiteColor]; - self.unreadLabel.userInteractionEnabled = YES; - - self.navigationItem.hidesBackButton = YES; - self.navigationItem.leftBarButtonItem = nil; - - if (self.presentingViewController && self.parentViewController) { - _unreadBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.unreadLabel]; - UIBarButtonItem *chatBackBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:AMLocalizedString(@"chat", @"Chat section header") style:UIBarButtonItemStylePlain target:self action:@selector(dismissChatRoom)]; - - self.leftBarButtonItems = @[chatBackBarButtonItem, self.unreadBarButtonItem]; - } else { - //TODO: leftItemsSupplementBackButton - UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 66, 44)]; - UIImage *image = [[UIImage imageNamed:@"backArrow"] imageFlippedForRightToLeftLayoutDirection]; - UIImageView *imageView = [[UIImageView alloc] initWithImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; - imageView.frame = CGRectMake(0, 10, 22, 22); - [view addGestureRecognizer:singleTap]; - [view addSubview:imageView]; - [view addSubview:self.unreadLabel]; - [imageView configureForAutoLayout]; - [imageView autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeTrailing]; - [imageView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeLeading ofView:self.unreadLabel]; - [self.unreadLabel configureForAutoLayout]; - [self.unreadLabel autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeLeading]; - - self.leftBarButtonItems = @[[[UIBarButtonItem alloc] initWithCustomView:view]]; - } - self.navigationItem.leftBarButtonItems = self.leftBarButtonItems; - self.stopInvitingContacts = NO; - - self.navigationController.interactivePopGestureRecognizer.delegate = nil; - + self.unreadMessages = self.chatRoom.unreadCount; self.attachmentMessages = [[NSMutableArray alloc] init]; // Avatar images @@ -218,9 +153,9 @@ - (void)viewDidLoad { self.avatarImages = [[NSMutableDictionary alloc] init]; _lastChatRoomStateString = @""; - _lastChatRoomStateColor = [UIColor whiteColor]; + _lastChatRoomStateColor = UIColor.whiteColor; if (self.chatRoom.isGroup) { - _peerAvatar = [UIImage imageForName:self.chatRoom.title.uppercaseString size:CGSizeMake(80.0f, 80.0f) backgroundColor:[UIColor mnz_gray999999] textColor:[UIColor whiteColor] font:[UIFont mnz_SFUIRegularWithSize:40.0f]]; + _peerAvatar = [UIImage imageForName:self.chatRoom.title.uppercaseString size:CGSizeMake(80.0f, 80.0f) backgroundColor:UIColor.mnz_gray999999 textColor:UIColor.whiteColor font:[UIFont mnz_SFUIRegularWithSize:40.0f]]; } else { _peerAvatar = [UIImage mnz_imageForUserHandle:[self.chatRoom peerHandleAtIndex:0] size:CGSizeMake(80.0f, 80.0f) delegate:nil]; } @@ -237,6 +172,11 @@ - (void)viewDidLoad { name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didBecomeActive) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + if (@available(iOS 10.0, *)) { [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[[MEGASdk base64HandleForUserHandle:self.chatRoom.chatId]]]; [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[[MEGASdk base64HandleForUserHandle:self.chatRoom.chatId]]]; @@ -244,8 +184,7 @@ - (void)viewDidLoad { } // Tap gesture for Jump to bottom view: - UITapGestureRecognizer *jumpButtonTap = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(jumpToBottomPressed:)]; + UITapGestureRecognizer *jumpButtonTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(jumpToBottomPressed:)]; [self.jumpToBottomView addGestureRecognizer:jumpButtonTap]; _whoIsTypingMutableArray = [[NSMutableArray alloc] init]; @@ -258,6 +197,8 @@ - (void)viewDidLoad { // Selection: self.selectingMessages = NO; self.selectedMessages = [[NSMutableArray alloc] init]; + + [self configureNavigationBar]; } - (void)viewWillAppear:(BOOL)animated { @@ -268,12 +209,35 @@ - (void)viewWillAppear:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(internetConnectionChanged) name:kReachabilityChangedNotification object:nil]; - [self customNavigationBarLabel]; - [self rightBarButtonItems]; [self updateUnreadLabel]; [self customForwardingToolbar]; self.inputToolbar.contentView.textView.text = [[MEGAStore shareInstance] fetchChatDraftWithChatId:self.chatRoom.chatId].text; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getDeliveredNotificationsWithCompletionHandler:^(NSArray *notifications) { + NSString *base64ChatId = [MEGASdk base64HandleForUserHandle:self.chatRoom.chatId]; + for (UNNotification *notification in notifications) { + if ([notification.request.identifier containsString:base64ChatId]) { + [center removeDeliveredNotificationsWithIdentifiers:@[notification.request.identifier]]; + } + } + }]; + + [center getPendingNotificationRequestsWithCompletionHandler:^(NSArray * _Nonnull requests) { + NSString *base64ChatId = [MEGASdk base64HandleForUserHandle:self.chatRoom.chatId]; + for (UNNotificationRequest *request in requests) { + if ([request.identifier containsString:base64ChatId]) { + [center removePendingNotificationRequestsWithIdentifiers:@[request.identifier]]; + } + } + }]; + + [self setLastMessageAsSeen]; + + if (!self.isMovingToParentViewController) { + [self customNavigationBarLabel]; + } } - (void)viewDidAppear:(BOOL)animated { @@ -291,6 +255,13 @@ - (void)willEnterForeground { offset.y = self.lastVerticalOffset; self.collectionView.contentOffset = offset; } + self.unreadMessages = self.chatRoom.unreadCount; +} + +- (void)didBecomeActive { + if (UIApplication.mnz_visibleViewController == self) { + [self setLastMessageAsSeen]; + } } - (void)willResignActive { @@ -337,8 +308,100 @@ - (void)dealloc { [self.observedNodeMessages removeAllObjects]; } +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + [coordinator animateAlongsideTransition:^(id context) { + [self configureNavigationBar]; + } completion:nil]; +} + #pragma mark - Private +- (void)configureNavigationBar { + self.navigationController.interactivePopGestureRecognizer.delegate = nil; + + self.navigationItem.hidesBackButton = YES; + self.navigationItem.leftBarButtonItem = nil; + + [self createLeftBarButtonItems]; + [self createRightBarButtonItems]; + if (@available(iOS 11.0, *)) { + [self initNavigationTitleViews]; + [self instantiateNavigationTitle]; + } + [self customNavigationBarLabel]; +} + +- (void)initNavigationTitleViews { + self.navigationTitleLabel = [[UILabel alloc] init]; + self.navigationTitleLabel.font = [UIFont mnz_SFUISemiBoldWithSize:15]; + self.navigationTitleLabel.textColor = UIColor.whiteColor; + + self.navigationStatusView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + [[self.navigationStatusView.widthAnchor constraintEqualToConstant:10] setActive:YES]; + [[self.navigationStatusView.heightAnchor constraintEqualToConstant:10] setActive:YES]; + self.navigationStatusView.layer.cornerRadius = 5; + self.navigationStatusView.layer.borderColor = UIColor.whiteColor.CGColor; + self.navigationStatusView.layer.borderWidth = 1; + self.navigationStatusView.backgroundColor = UIColor.mnz_green00BFA5; + + self.navigationSubtitleLabel = [[UILabel alloc] init]; + self.navigationSubtitleLabel.font = [UIFont mnz_SFUIRegularWithSize:12]; + self.navigationSubtitleLabel.textColor = UIColor.mnz_grayE3E3E3; +} + +- (void)instantiateNavigationTitle { + float leftBarButtonsWidth = 25; //25 is by the leading margin + for (UIBarButtonItem *barButton in self.leftBarButtonItems) { + leftBarButtonsWidth += barButton.customView.frame.size.width; + } + + self.navigationView = [[UIView alloc] initWithFrame:CGRectMake(0, 4, self.navigationController.navigationBar.bounds.size.width - leftBarButtonsWidth - 50 * (self.navigationItem.rightBarButtonItems.count), 36)]; + self.navigationView.clipsToBounds = YES; + self.navigationView.userInteractionEnabled = YES; + [self.navigationItem setTitleView:self.navigationView]; + + [[self.navigationView.widthAnchor constraintEqualToConstant:self.navigationItem.titleView.bounds.size.width] setActive:YES]; + [[self.navigationView.heightAnchor constraintEqualToConstant:self.navigationItem.titleView.bounds.size.height] setActive:YES]; + + UIStackView *mainStackView = [[UIStackView alloc] init]; + mainStackView.distribution = UIStackViewDistributionEqualSpacing; + mainStackView.alignment = UIStackViewAlignmentLeading; + mainStackView.translatesAutoresizingMaskIntoConstraints = false; + mainStackView.spacing = 4; + + UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation)) { + UIStackView *titleView = [[UIStackView alloc] init]; + titleView.axis = UILayoutConstraintAxisHorizontal; + titleView.distribution = UIStackViewDistributionEqualSpacing; + titleView.alignment = UIStackViewAlignmentCenter; + titleView.spacing = 8; + [titleView addArrangedSubview:self.navigationTitleLabel]; + [titleView addArrangedSubview:self.navigationStatusView]; + titleView.translatesAutoresizingMaskIntoConstraints = false; + + mainStackView.axis = UILayoutConstraintAxisVertical; + [mainStackView addArrangedSubview:titleView]; + [mainStackView addArrangedSubview:self.navigationSubtitleLabel]; + [self.navigationView addSubview:mainStackView]; + [[mainStackView.trailingAnchor constraintEqualToAnchor:self.navigationView.trailingAnchor] setActive:YES]; + } else { + mainStackView.axis = UILayoutConstraintAxisHorizontal; + mainStackView.alignment = UIStackViewAlignmentCenter; + mainStackView.spacing = 8; + [mainStackView addArrangedSubview:self.navigationTitleLabel]; + [mainStackView addArrangedSubview:self.navigationStatusView]; + [mainStackView addArrangedSubview:self.navigationSubtitleLabel]; + [self.navigationView addSubview:mainStackView]; + } + + [[mainStackView.leadingAnchor constraintEqualToAnchor:self.navigationView.leadingAnchor] setActive:YES]; + [[mainStackView.topAnchor constraintEqualToAnchor:self.navigationView.topAnchor] setActive:YES]; + [[mainStackView.bottomAnchor constraintEqualToAnchor:self.navigationView.bottomAnchor] setActive:YES]; +} + - (void)loadMessages { NSUInteger messagesToLoad = 32; if (self.isFirstLoad && (self.unreadMessages > 32 || self.unreadMessages < 0)) { @@ -346,16 +409,21 @@ - (void)loadMessages { } NSInteger loadMessage = [[MEGASdkManager sharedMEGAChatSdk] loadMessagesForChat:self.chatRoom.chatId count:messagesToLoad]; switch (loadMessage) { + case -1: + MEGALogDebug(@"loadMessagesForChat: history has to be fetched from server, but we are not logged in yet"); + self.loadMessagesLater = YES; + break; + case 0: - MEGALogDebug(@"There's no more history available"); + MEGALogDebug(@"loadMessagesForChat: there's no more history available (not even in the server)"); break; case 1: - MEGALogDebug(@"Messages will be fetched locally"); + MEGALogDebug(@"loadMessagesForChat: messages will be fetched locally"); break; case 2: - MEGALogDebug(@"Messages will be requested to the server"); + MEGALogDebug(@"loadMessagesForChat: messages will be requested to the server"); break; default: @@ -364,14 +432,14 @@ - (void)loadMessages { } - (void)customNavigationBarLabel { - UILabel *label = [[UILabel alloc] init]; if (self.selectingMessages) { self.inputToolbar.hidden = YES; - label = [Helper customNavigationBarLabelWithTitle:[NSString stringWithFormat:AMLocalizedString(@"xSelected", nil), self.selectedMessages.count] subtitle:@""]; + UILabel *label = [Helper customNavigationBarLabelWithTitle:[NSString stringWithFormat:AMLocalizedString(@"xSelected", nil), self.selectedMessages.count] subtitle:@""]; self.navigationItem.leftBarButtonItems = @[]; + [self.navigationItem setTitleView:label]; } else { self.inputToolbar.hidden = self.chatRoom.ownPrivilege <= MEGAChatRoomPrivilegeRo; @@ -379,62 +447,96 @@ - (void)customNavigationBarLabel { NSString *chatRoomState; if (self.chatRoom.archived) { + self.navigationStatusView.hidden = YES; chatRoomState = AMLocalizedString(@"archived", @"Title of flag of archived chats."); } else { - MEGAChatConnection connectionState = [[MEGASdkManager sharedMEGAChatSdk] chatConnectionState:self.chatRoom.chatId]; - switch (connectionState) { - case MEGAChatConnectionOffline: - case MEGAChatConnectionInProgress: - case MEGAChatConnectionLogging: - chatRoomState = AMLocalizedString(@"connecting", nil); - self.lastChatRoomStateColor = [UIColor mnz_colorForStatusChange:MEGAChatStatusOffline]; - - break; - - case MEGAChatConnectionOnline: - if (self.chatRoom.isGroup) { - if (self.chatRoom.ownPrivilege <= MEGAChatRoomPrivilegeRo) { - chatRoomState = AMLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with"); - } - } else { - if (self.chatRoom.ownPrivilege <= MEGAChatRoomPrivilegeRo) { - chatRoomState = AMLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with"); - } else { - chatRoomState = [NSString chatStatusString:[[MEGASdkManager sharedMEGAChatSdk] userOnlineStatus:[self.chatRoom peerHandleAtIndex:0]]]; - self.lastChatRoomStateColor = [UIColor mnz_colorForStatusChange:[[MEGASdkManager sharedMEGAChatSdk] userOnlineStatus:[self.chatRoom peerHandleAtIndex:0]]]; - } + if (self.chatRoom.isGroup) { + chatRoomState = [self participantsNames]; + self.navigationStatusView.hidden = YES; + self.navigationSubtitleLabel.hidden = NO; + } else { + MEGAChatStatus userStatus = [MEGASdkManager.sharedMEGAChatSdk userOnlineStatus:[self.chatRoom peerHandleAtIndex:0]]; + if (userStatus != MEGAChatStatusInvalid) { + self.navigationStatusView.hidden = NO; + self.navigationSubtitleLabel.hidden = NO; + if (userStatus < MEGAChatStatusOnline) { + [MEGASdkManager.sharedMEGAChatSdk requestLastGreen:[self.chatRoom peerHandleAtIndex:0]]; } - - break; + self.navigationStatusView.backgroundColor = [UIColor mnz_colorForStatusChange:userStatus]; + chatRoomState = [NSString chatStatusString:userStatus]; + } else { + self.navigationStatusView.hidden = YES; + self.navigationSubtitleLabel.hidden = YES; + } } } - if (chatRoomState) { - label = [Helper customNavigationBarLabelWithTitle:chatRoomTitle subtitle:chatRoomState]; - self.lastChatRoomStateString = chatRoomState; + UITapGestureRecognizer *titleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(chatRoomTitleDidTap)]; + + if (@available(iOS 11.0, *)) { + self.navigationTitleLabel.text = chatRoomTitle; + self.navigationSubtitleLabel.text = chatRoomState; + self.navigationView.gestureRecognizers = @[titleTapRecognizer]; } else { - label = [Helper customNavigationBarLabelWithTitle:chatRoomTitle subtitle:@""]; + self.navigationTitleLabel = [UILabel new]; + if (chatRoomState && !self.chatRoom.isGroup) { + self.navigationTitleLabel = [Helper customNavigationBarLabelWithTitle:chatRoomTitle subtitle:chatRoomState]; + } else { + self.navigationTitleLabel = [Helper customNavigationBarLabelWithTitle:chatRoomTitle subtitle:@""]; + } + + self.navigationTitleLabel.userInteractionEnabled = YES; + self.navigationTitleLabel.superview.userInteractionEnabled = YES; + self.navigationTitleLabel.gestureRecognizers = @[titleTapRecognizer]; + self.navigationTitleLabel.adjustsFontSizeToFitWidth = YES; + self.navigationTitleLabel.minimumScaleFactor = 0.8f; + self.navigationTitleLabel.frame = CGRectMake(0, 0, self.navigationItem.titleView.bounds.size.width, 44); + + [self.navigationItem setTitleView:self.navigationTitleLabel]; } - label.userInteractionEnabled = YES; - label.superview.userInteractionEnabled = YES; - - UITapGestureRecognizer *titleTapRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(chatRoomTitleDidTap)]; - label.gestureRecognizers = @[titleTapRecognizer]; - + self.lastChatRoomStateString = chatRoomState; self.navigationItem.leftBarButtonItems = self.leftBarButtonItems; } - label.adjustsFontSizeToFitWidth = YES; - label.minimumScaleFactor = 0.8f; - label.frame = CGRectMake(0, 0, self.navigationItem.titleView.bounds.size.width, 44); - - [self.navigationItem setTitleView:label]; [self updateCollectionViewInsets]; } -- (void)rightBarButtonItems { +- (void)createLeftBarButtonItems { + UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(popViewController)]; + + self.unreadLabel = [[UILabel alloc] initWithFrame:CGRectMake(25, 6, 30, 30)]; + self.unreadLabel.font = [UIFont mnz_SFUIMediumWithSize:12.0f]; + self.unreadLabel.textColor = UIColor.whiteColor; + self.unreadLabel.userInteractionEnabled = YES; + + if (self.presentingViewController && self.parentViewController) { + self.unreadBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.unreadLabel]; + UIBarButtonItem *chatBackBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:AMLocalizedString(@"chat", @"Chat section header") style:UIBarButtonItemStylePlain target:self action:@selector(dismissChatRoom)]; + + self.leftBarButtonItems = @[chatBackBarButtonItem, self.unreadBarButtonItem]; + } else { + //TODO: leftItemsSupplementBackButton + UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 66, 44)]; + UIImage *image = [[UIImage imageNamed:@"backArrow"] imageFlippedForRightToLeftLayoutDirection]; + UIImageView *imageView = [[UIImageView alloc] initWithImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; + imageView.frame = CGRectMake(0, 10, 22, 22); + imageView.contentMode = UIViewContentModeScaleAspectFit; + [view addGestureRecognizer:singleTap]; + [view addSubview:imageView]; + [view addSubview:self.unreadLabel]; + [imageView configureForAutoLayout]; + [imageView autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeTrailing]; + [imageView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeLeading ofView:self.unreadLabel]; + [self.unreadLabel configureForAutoLayout]; + [self.unreadLabel autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeLeading]; + + self.leftBarButtonItems = @[[[UIBarButtonItem alloc] initWithCustomView:view]]; + } + self.navigationItem.leftBarButtonItems = self.leftBarButtonItems; +} + +- (void)createRightBarButtonItems { if (self.selectingMessages) { UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:AMLocalizedString(@"cancel", nil) style:UIBarButtonItemStyleDone target:self action:@selector(cancelSelecting:)]; self.navigationItem.rightBarButtonItems = @[cancelBarButtonItem]; @@ -507,7 +609,7 @@ - (void)presentAddOrAttachParticipantToGroup:(UIBarButtonItem *)sender { [self.participantsMutableDictionary setObject:[NSNumber numberWithUnsignedLongLong:peerHandle] forKey:[NSNumber numberWithUnsignedLongLong:peerHandle]]; } } - contactsVC.participantsMutableDictionary = [self.participantsMutableDictionary copy]; + contactsVC.participantsMutableDictionary = self.participantsMutableDictionary.copy; contactsVC.userSelected = ^void(NSArray *users, NSString *groupName) { if (addParticipant) { @@ -536,12 +638,40 @@ - (void)presentAddOrAttachParticipantToGroup:(UIBarButtonItem *)sender { - (void)updateUnreadLabel { NSInteger unreadChats = [[MEGASdkManager sharedMEGAChatSdk] unreadChats]; - NSString *unreadChatsString = unreadChats ? [NSString stringWithFormat:@"(%ld)", unreadChats] : nil; + NSString *unreadChatsString = unreadChats ? [NSString stringWithFormat:@"(%td)", unreadChats] : nil; self.unreadLabel.text = unreadChatsString; } +- (void)setupCollectionView { + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideInputToolbar)]; + tapGesture.cancelsTouchesInView = NO; + [self.collectionView addGestureRecognizer:tapGesture]; + + [self customiseCollectionViewLayout]; + + [self.collectionView registerNib:[MEGAOpenMessageHeaderView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[MEGAOpenMessageHeaderView headerReuseIdentifier]]; + [self.collectionView registerNib:[MEGAMessagesTypingIndicatorFoorterView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[MEGAMessagesTypingIndicatorFoorterView footerReuseIdentifier]]; + + self.collectionView.accessoryDelegate = self; + self.collectionView.backgroundColor = UIColor.whiteColor; + + //Register custom menu actions for cells. + [JSQMessagesCollectionViewCell registerMenuAction:@selector(edit:message:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(forward:message:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(import:message:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(download:message:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(addContact:message:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(revoke:message:indexPath:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(removeRichPreview:message:indexPath:)]; + [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; + + JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:[UIImage imageNamed:@"bubble_tailless"] capInsets:UIEdgeInsetsZero layoutDirection:UIApplication.sharedApplication.userInterfaceLayoutDirection]; + self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:UIColor.mnz_green00BFA5]; + self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:UIColor.mnz_grayE2EAEA]; +} + - (void)customiseCollectionViewLayout { - self.collectionView.collectionViewLayout.messageBubbleFont = [UIFont mnz_SFUIRegularWithSize:15.0f]; + self.collectionView.collectionViewLayout.messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; self.collectionView.collectionViewLayout.messageBubbleTextViewFrameInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f); self.collectionView.collectionViewLayout.messageBubbleTextViewTextContainerInsets = UIEdgeInsetsMake(10.0f, 10.0f, 10.0f, 10.0f); @@ -555,10 +685,11 @@ - (void)customiseCollectionViewLayout { } - (void)customToolbarContentView { - self.inputToolbar.contentView.textView.placeHolderTextColor = [UIColor mnz_grayCCCCCC]; + self.inputToolbar.contentView.textView.jsq_pasteDelegate = self; + self.inputToolbar.contentView.textView.placeHolderTextColor = UIColor.mnz_grayCCCCCC; self.inputToolbar.contentView.textView.font = [UIFont mnz_SFUIRegularWithSize:15.0f]; - self.inputToolbar.contentView.textView.textColor = [UIColor mnz_black333333]; - self.inputToolbar.contentView.textView.tintColor = [UIColor mnz_green00BFA5]; + self.inputToolbar.contentView.textView.textColor = UIColor.mnz_black333333; + self.inputToolbar.contentView.textView.tintColor = UIColor.mnz_green00BFA5; [self updateToolbarPlaceHolder]; self.inputToolbar.contentView.textView.delegate = self; self.inputToolbar.contentView.textView.text = [[MEGAStore shareInstance] fetchChatDraftWithChatId:self.chatRoom.chatId].text; @@ -662,11 +793,7 @@ - (void)chatRoomTitleDidTap { } } -- (void)setChatOpenMessageForIndexPath:(NSIndexPath *)indexPath { - if (self.openMessageHeaderView == nil) { - self.openMessageHeaderView = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MEGAOpenMessageHeaderViewID" forIndexPath:indexPath]; - } - +- (NSString *)participantsNames { NSString *participantsNames = @""; for (NSUInteger i = 0; i < self.chatRoom.peerCount; i++) { NSString *peerName; @@ -690,9 +817,16 @@ - (void)setChatOpenMessageForIndexPath:(NSIndexPath *)indexPath { participantsNames = [participantsNames stringByAppendingString:[NSString stringWithFormat:@"%@, ", peerName]]; } } + return participantsNames; +} + +- (void)setChatOpenMessageForIndexPath:(NSIndexPath *)indexPath { + if (self.openMessageHeaderView == nil) { + self.openMessageHeaderView = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MEGAOpenMessageHeaderViewID" forIndexPath:indexPath]; + } self.openMessageHeaderView.chattingWithLabel.text = AMLocalizedString(@"chattingWith", @"Title show above the name of the persons with whom you're chatting"); - self.openMessageHeaderView.conversationWithLabel.text = participantsNames; + self.openMessageHeaderView.conversationWithLabel.text = [self participantsNames]; self.openMessageHeaderView.onlineStatusLabel.text = self.lastChatRoomStateString; self.openMessageHeaderView.onlineStatusView.backgroundColor = self.lastChatRoomStateColor; self.openMessageHeaderView.conversationWithAvatar.image = self.chatRoom.isGroup ? nil : self.peerAvatar; @@ -703,7 +837,7 @@ - (void)setChatOpenMessageForIndexPath:(NSIndexPath *)indexPath { confidentialityExplanationString = [confidentialityExplanationString stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"[S]%@[/S]", confidentialityString] withString:@""]; NSMutableAttributedString *confidentialityAttributedString = [[NSMutableAttributedString alloc] initWithString:confidentialityString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:UIColor.mnz_redMain}]; - NSMutableAttributedString *confidentialityExplanationAttributedString = [[NSMutableAttributedString alloc] initWithString:confidentialityExplanationString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:[UIColor mnz_gray777777]}]; + NSMutableAttributedString *confidentialityExplanationAttributedString = [[NSMutableAttributedString alloc] initWithString:confidentialityExplanationString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:UIColor.mnz_gray777777}]; [confidentialityAttributedString appendAttributedString:confidentialityExplanationAttributedString]; self.openMessageHeaderView.confidentialityLabel.attributedText = confidentialityAttributedString; @@ -712,7 +846,7 @@ - (void)setChatOpenMessageForIndexPath:(NSIndexPath *)indexPath { authenticityExplanationString = [authenticityExplanationString stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"[S]%@[/S]", authenticityString] withString:@""]; NSMutableAttributedString *authenticityAttributedString = [[NSMutableAttributedString alloc] initWithString:authenticityString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:UIColor.mnz_redMain}]; - NSMutableAttributedString *authenticityExplanationAttributedString = [[NSMutableAttributedString alloc] initWithString:authenticityExplanationString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:[UIColor mnz_gray777777]}]; + NSMutableAttributedString *authenticityExplanationAttributedString = [[NSMutableAttributedString alloc] initWithString:authenticityExplanationString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:15.0f], NSForegroundColorAttributeName:UIColor.mnz_gray777777}]; [authenticityAttributedString appendAttributedString:authenticityExplanationAttributedString]; self.openMessageHeaderView.authenticityLabel.attributedText = authenticityAttributedString; } @@ -847,15 +981,15 @@ - (void)showProgressViewUnderNavigationBar { self.navigationBarProgressView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; self.navigationBarProgressView.frame = CGRectMake(self.navigationController.navigationBar.bounds.origin.x, self.navigationController.navigationBar.bounds.size.height, self.navigationController.navigationBar.bounds.size.width, 2.0f); self.navigationBarProgressView.transform = CGAffineTransformScale(self.navigationBarProgressView.transform, 1, 2); - self.navigationBarProgressView.progressTintColor = [UIColor mnz_green00BFA5]; - self.navigationBarProgressView.trackTintColor = [UIColor clearColor]; + self.navigationBarProgressView.progressTintColor = UIColor.mnz_green00BFA5; + self.navigationBarProgressView.trackTintColor = UIColor.clearColor; dispatch_async(dispatch_get_main_queue(), ^{ self.navigationBarProgressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; self.navigationBarProgressView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; self.navigationBarProgressView.frame = CGRectMake(self.navigationController.navigationBar.bounds.origin.x, self.navigationController.navigationBar.bounds.size.height, self.navigationController.navigationBar.bounds.size.width, 2.0f); - self.navigationBarProgressView.progressTintColor = [UIColor mnz_green00BFA5]; - self.navigationBarProgressView.trackTintColor = [UIColor clearColor]; + self.navigationBarProgressView.progressTintColor = UIColor.mnz_green00BFA5; + self.navigationBarProgressView.trackTintColor = UIColor.clearColor; [self.navigationController.navigationBar addSubview:self.navigationBarProgressView]; }); @@ -878,7 +1012,7 @@ - (void)handleTruncateMessage:(MEGAChatMessage *)message { } - (void)internetConnectionChanged { - self.audioCallBarButtonItem.enabled = self.videoCallBarButtonItem.enabled = ((self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeStandard) && [MEGAReachabilityManager isReachable]); + self.audioCallBarButtonItem.enabled = self.videoCallBarButtonItem.enabled = ((self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeStandard) && MEGAReachabilityManager.isReachable); [self customNavigationBarLabel]; @@ -1002,7 +1136,7 @@ - (void)updateUnreadMessagesLabel:(NSUInteger)unreads { } - (void)updateOffsetForCellAtIndexPath:(NSIndexPath *)indexPath previousHeight:(CGFloat)previousHeight { - if ([[self.collectionView indexPathsForVisibleItems] containsObject:indexPath]) { + if ([self.collectionView.indexPathsForVisibleItems containsObject:indexPath]) { UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; CGFloat currentHeight = cell.frame.size.height; CGFloat verticalIncrement = currentHeight - previousHeight; @@ -1017,6 +1151,15 @@ - (void)updateCollectionViewInsets { [self jsq_setCollectionViewInsetsTopValue:0.0f bottomValue:self.lastBottomInset]; } +- (void)setLastMessageAsSeen { + if (self.messages.count > 0) { + MEGAChatMessage *lastMessage = self.messages.lastObject; + if (lastMessage.userHandle != [MEGASdkManager sharedMEGAChatSdk].myUserHandle && [[MEGASdkManager sharedMEGAChatSdk] lastChatMessageSeenForChat:self.chatRoom.chatId].messageId != lastMessage.messageId) { + [[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:lastMessage.messageId]; + } + } +} + #pragma mark - Gesture recognizer - (void)hideInputToolbar { @@ -1151,10 +1294,11 @@ - (void)cancelSelecting:(id)sender { self.selectingMessages = NO; [self.selectedMessages removeAllObjects]; [self.collectionView reloadItemsAtIndexPaths:self.collectionView.indexPathsForVisibleItems]; - [self rightBarButtonItems]; + [self createRightBarButtonItems]; [self.navigationController setToolbarHidden:YES animated:YES]; + [self instantiateNavigationTitle]; [self customNavigationBarLabel]; } @@ -1180,7 +1324,7 @@ - (void)forwardSelectedMessages { UINavigationController *sendToNC = [chatStoryboard instantiateViewControllerWithIdentifier:@"SendToNavigationControllerID"]; SendToViewController *sendToViewController = sendToNC.viewControllers.firstObject; sendToViewController.sendMode = SendModeForward; - sendToViewController.messages = [self.selectedMessages copy]; + sendToViewController.messages = self.selectedMessages.copy; sendToViewController.sourceChatId = self.chatRoom.chatId; sendToViewController.completion = ^(NSArray *chatIdNumbers, NSArray *sentMessages) { BOOL selfForwarded = NO, showSuccess = NO; @@ -1255,15 +1399,49 @@ - (void)shareSelectedMessages:(UIBarButtonItem *)sender { #pragma mark - JSQMessagesViewController method overrides - (void)didPressSendButton:(UIButton *)button - withMessageText:(NSString *)text + withMessageText:(NSString *)messageText senderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date { - if (text.mnz_isEmpty) { + if (messageText.mnz_isEmpty) { return; } + // Emoji replacement: + NSString *text = messageText; + NSDictionary *emojiDict = @{@":\\)" : @"🙂", // :) + @":-\\)" : @"🙂", // :-) + @":d" : @"😀", + @":-d" : @"😀", + @";\\)" : @"😉", // ;) + @";-\\)" : @"😉", // ;-) + @";p" : @"😜", + @";-p" : @"😜", + @":p" : @"😛", + @":-p" : @"😛", + @":\\(" : @"🙁", // :( + @":\\\\" : @"😕", // colon+backslash + @":/" : @"😕", + @":\\|" : @"😐", // :| + @"d:" : @"😧", + @":o" : @"😮"}; + for (NSString *key in emojiDict.allKeys) { + NSString *replacement = [emojiDict objectForKey:key]; + NSString *pattern = [NSString stringWithFormat:@"(?>\\s+|^)(%@)(?>\\s+|$)", key]; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:(NSRegularExpressionAnchorsMatchLines | NSRegularExpressionCaseInsensitive) error:nil]; + NSUInteger padding = 0; + NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, text.length)]; + for (NSTextCheckingResult *result in matches) { + if (result.numberOfRanges > 1) { + NSRange range = [result rangeAtIndex:1]; + range.location -= padding; + padding += range.length - replacement.length; + text = [text stringByReplacingCharactersInRange:range withString:replacement]; + } + } + } + MEGAChatMessage *message = [self sendMessage:text]; if ([MEGAChatSdk hasUrl:text]) { @@ -1369,13 +1547,13 @@ - (void)didPressAccessoryButton:(UIButton *)sender { } }; }]; - [sendFromCloudDriveAlertAction setValue:[UIColor mnz_black333333] forKey:@"titleTextColor"]; + [sendFromCloudDriveAlertAction setValue:UIColor.mnz_black333333 forKey:@"titleTextColor"]; [selectOptionAlertController addAction:sendFromCloudDriveAlertAction]; UIAlertAction *sendContactAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"contact", @"referring to a contact in the contact list of the user") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self presentAddOrAttachParticipantToGroup:nil]; }]; - [sendContactAlertAction setValue:[UIColor mnz_black333333] forKey:@"titleTextColor"]; + [sendContactAlertAction setValue:UIColor.mnz_black333333 forKey:@"titleTextColor"]; [selectOptionAlertController addAction:sendContactAlertAction]; selectOptionAlertController.modalPresentationStyle = UIModalPresentationOverCurrentContext; @@ -1428,7 +1606,7 @@ - (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top bottomValue:(CGFloat)bo #pragma mark - JSQMessages CollectionView DataSource - (NSString *)senderId { - return [NSString stringWithFormat:@"%llu", [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]]; + return [NSString stringWithFormat:@"%llu", [MEGASdkManager sharedMEGAChatSdk].myUserHandle]; } - (NSString *)senderDisplayName { @@ -1466,7 +1644,7 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMess - (id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { MEGAChatMessage *message = [self.messages objectAtIndex:indexPath.item]; - if (message.userHandle == [[MEGASdkManager sharedMEGAChatSdk] myUserHandle] || message.type == MEGAChatMessageTypeCallEnded) { + if (message.userHandle == [MEGASdkManager sharedMEGAChatSdk].myUserHandle || message.type == MEGAChatMessageTypeCallEnded) { return nil; } if (indexPath.item < self.messages.count-1) { @@ -1475,7 +1653,7 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMess return nil; } } - NSNumber *avatarKey = [NSNumber numberWithUnsignedLong:message.userHandle]; + NSNumber *avatarKey = @(message.userHandle); UIImage *avatar = [self.avatarImages objectForKey:avatarKey]; if (!avatar) { avatar = [UIImage mnz_imageForUserHandle:message.userHandle size:CGSizeMake(kAvatarImageDiameter, kAvatarImageDiameter) delegate:nil]; @@ -1510,7 +1688,7 @@ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionVi if (showDayMonthYear) { NSString *dateString = [[JSQMessagesTimestampFormatter sharedFormatter] relativeDateForDate:message.date]; - NSAttributedString *dateAttributedString = [[NSAttributedString alloc] initWithString:dateString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIMediumWithSize:12.0f], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + NSAttributedString *dateAttributedString = [[NSAttributedString alloc] initWithString:dateString attributes:@{NSFontAttributeName:[UIFont mnz_SFUIMediumWithSize:12.0f], NSForegroundColorAttributeName:UIColor.mnz_black333333}]; return dateAttributedString; } @@ -1524,7 +1702,7 @@ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionVi BOOL showMessageBubbleTopLabel = [self showHourForMessage:message withIndexPath:indexPath]; if (showMessageBubbleTopLabel) { NSString *hour = [[JSQMessagesTimestampFormatter sharedFormatter] timeForDate:message.date]; - NSAttributedString *hourAttributed = [[NSAttributedString alloc] initWithString:hour attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:12.0f], NSForegroundColorAttributeName:[UIColor grayColor]}]; + NSAttributedString *hourAttributed = [[NSAttributedString alloc] initWithString:hour attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:UIColor.grayColor}]; NSMutableAttributedString *topCellAttributed = [[NSMutableAttributedString alloc] init]; if (self.chatRoom.isGroup && !message.isManagementMessage) { @@ -1535,7 +1713,7 @@ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionVi fullname = @""; } } - NSAttributedString *fullnameAttributed = [[NSAttributedString alloc] initWithString:[fullname stringByAppendingString:@" "] attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:12.0f], NSForegroundColorAttributeName:[UIColor grayColor]}]; + NSAttributedString *fullnameAttributed = [[NSAttributedString alloc] initWithString:[fullname stringByAppendingString:@" "] attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:UIColor.grayColor}]; [topCellAttributed appendAttributedString:fullnameAttributed]; [topCellAttributed appendAttributedString:hourAttributed]; } else { @@ -1572,16 +1750,17 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection cell.accessoryButton.hidden = YES; if (message.isDeleted) { - cell.textView.font = [UIFont mnz_SFUIRegularItalicWithSize:15.0f]; - cell.textView.textColor = [UIColor mnz_blue2BA6DE]; + cell.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline].italic; + cell.textView.textColor = UIColor.mnz_blue2BA6DE; } else if (message.isManagementMessage) { - cell.textView.linkTextAttributes = @{NSForegroundColorAttributeName: [UIColor mnz_black333333], - NSUnderlineColorAttributeName: [UIColor mnz_black333333], + cell.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; + cell.textView.linkTextAttributes = @{NSForegroundColorAttributeName: UIColor.mnz_black333333, + NSUnderlineColorAttributeName: UIColor.mnz_black333333, NSUnderlineStyleAttributeName: @(NSUnderlineStyleNone)}; cell.textView.attributedText = message.attributedText; } else if (!message.isMediaMessage) { - cell.textView.font = [UIFont mnz_SFUIRegularWithSize:15.0f]; - cell.textView.textColor = [message.senderId isEqualToString:self.senderId] ? [UIColor whiteColor] : [UIColor mnz_black333333]; + cell.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; + cell.textView.textColor = [message.senderId isEqualToString:self.senderId] ? UIColor.whiteColor : UIColor.mnz_black333333; cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor, NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; @@ -1597,7 +1776,7 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection cell.contentView.alpha = 1.0f; } - if ([cell.textView.text mnz_isPureEmojiString]) { + if (cell.textView.text.mnz_isPureEmojiString) { cell.messageBubbleImageView.image = nil; cell.textView.font = [UIFont mnz_defaultFontForPureEmojiStringWithEmojis:[cell.textView.text mnz_emojiCount]]; } else if (message.attributedText.length > 0) { @@ -1829,7 +2008,7 @@ - (void)forward:(id)sender message:(MEGAChatMessage *)message { self.selectingMessages = YES; [self.selectedMessages addObject:message]; [self.collectionView reloadItemsAtIndexPaths:self.collectionView.indexPathsForVisibleItems]; - [self rightBarButtonItems]; + [self createRightBarButtonItems]; if (self.inputToolbar.contentView.textView.isFirstResponder) { [self.inputToolbar.contentView.textView resignFirstResponder]; @@ -1913,10 +2092,15 @@ - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView BOOL showMessageBubleTopLabel = [self showHourForMessage:message withIndexPath:indexPath]; if (showMessageBubleTopLabel) { + NSAttributedString *bubbleTopString = [self collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath]; + CGFloat boundingWidth = collectionViewLayout.itemWidth - 28; + NSInteger boundingHeight = CGRectIntegral([bubbleTopString boundingRectWithSize:CGSizeMake(boundingWidth, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + context:nil]).size.height; if (self.chatRoom.isGroup) { - height = kGroupChatCellLabelHeight; + height = boundingHeight + kGroupChatCellLabelHeightBuffer; } else { - height = k1on1CellLabelHeight; + height = boundingHeight + k1on1CellLabelHeightBuffer; } } @@ -1954,6 +2138,7 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessage } MEGAPhotoBrowserViewController *photoBrowserVC = [MEGAPhotoBrowserViewController photoBrowserWithMediaNodes:mediaNodesArray api:[MEGASdkManager sharedMEGASdk] displayMode:DisplayModeSharedItem presentingNode:nil preferredIndex:[reverseArray indexOfObject:message]]; + photoBrowserVC.delegate = self; [self.navigationController presentViewController:photoBrowserVC animated:YES completion:nil]; } else { @@ -1982,9 +2167,12 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessage [self.navigationController pushViewController:chatAttachedContactsVC animated:YES]; } } else if (message.type == MEGAChatMessageTypeContainsMeta) { - [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:message.containsMeta.richPreview.url]]; + NSURL *url = [NSURL URLWithString:message.containsMeta.richPreview.url]; + MEGALinkManager.linkURL = url; + [MEGALinkManager processLinkURL:url]; } else if (message.node) { - [message.MEGALink mnz_showLinkView]; + MEGALinkManager.linkURL = message.MEGALink; + [MEGALinkManager processLinkURL:message.MEGALink]; } } } @@ -2001,7 +2189,7 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtI #pragma mark - JSQMessagesComposerTextViewPasteDelegate methods - (BOOL)composerTextView:(JSQMessagesComposerTextView *)textView shouldPasteWithSender:(id)sender { - if ([UIPasteboard generalPasteboard].image) { + if (UIPasteboard.generalPasteboard.image) { return NO; } return YES; @@ -2056,7 +2244,7 @@ - (void)messageView:(JSQMessagesCollectionView *)view didTapAccessoryButtonAtInd break; } }]; - [retryAlertAction setValue:[UIColor mnz_black333333] forKey:@"titleTextColor"]; + [retryAlertAction setValue:UIColor.mnz_black333333 forKey:@"titleTextColor"]; [alertController addAction:retryAlertAction]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"deleteMessage", @"Button which allows to delete message in chat conversation.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { @@ -2066,7 +2254,7 @@ - (void)messageView:(JSQMessagesCollectionView *)view didTapAccessoryButtonAtInd }]]; alertController.modalPresentationStyle = UIModalPresentationPopover; - UIPopoverPresentationController *popoverPresentationController = [alertController popoverPresentationController]; + UIPopoverPresentationController *popoverPresentationController = alertController.popoverPresentationController; CGRect deleteRect = [[view cellForItemAtIndexPath:path] bounds]; popoverPresentationController.sourceRect = deleteRect; popoverPresentationController.sourceView = [view cellForItemAtIndexPath:path]; @@ -2084,7 +2272,7 @@ - (void)messageView:(JSQMessagesCollectionView *)view didTapAccessoryButtonAtInd - (void)textViewDidChange:(UITextView *)textView { [super textViewDidChange:textView]; NSInteger textLength = textView.text.length; - if (textLength > 0 && ![self.sendTypingTimer isValid]) { + if (textLength > 0 && !self.sendTypingTimer.isValid) { self.sendTypingTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(doNothing) userInfo:nil repeats:NO]; [[MEGASdkManager sharedMEGAChatSdk] sendTypingNotificationForChat:self.chatRoom.chatId]; } else if (textLength == 0) { @@ -2096,6 +2284,12 @@ - (void)textViewDidEndEditing:(UITextView *)textView { [[MEGAStore shareInstance] insertOrUpdateChatDraftWithChatId:self.chatRoom.chatId text:self.inputToolbar.contentView.textView.text]; } +#pragma mark - MEGAPhotoBrowserDelegate + +- (void)photoBrowser:(MEGAPhotoBrowserViewController *)photoBrowser willDismissWithNode:(MEGANode *)node { + [self setLastMessageAsSeen]; +} + #pragma mark - MEGAChatRoomDelegate - (void)onMessageReceived:(MEGAChatSdk *)api message:(MEGAChatMessage *)message { @@ -2114,18 +2308,27 @@ - (void)onMessageReceived:(MEGAChatSdk *)api message:(MEGAChatMessage *)message case MEGAChatMessageTypeContact: case MEGAChatMessageTypeCallEnded: case MEGAChatMessageTypeContainsMeta: { + NSUInteger unreads; + if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive && UIApplication.mnz_visibleViewController == self) { + [[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:message.messageId]; + unreads = [message.senderId isEqualToString:self.senderId] ? 0 : self.unreadMessages + 1; + } else { + self.chatRoom = [api chatRoomForChatId:self.chatRoom.chatId]; + self.unreadMessages = self.chatRoom.unreadCount; + unreads = [message.senderId isEqualToString:self.senderId] ? 0 : self.unreadMessages; + } + [self.messages addObject:message]; [self finishReceivingMessage]; - NSUInteger unreads = [message.senderId isEqualToString:self.senderId] ? 0 : self.unreadMessages + 1; [self updateUnreadMessagesLabel:unreads]; dispatch_async(dispatch_get_main_queue(), ^{ NSUInteger items = [self.collectionView numberOfItemsInSection:0]; - NSUInteger visibleItems = [self.collectionView indexPathsForVisibleItems].count; + NSUInteger visibleItems = self.collectionView.indexPathsForVisibleItems.count; if (items > 1 && visibleItems > 0) { NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:(items - 2) inSection:0]; - if ([[self.collectionView indexPathsForVisibleItems] containsObject:lastCellIndexPath]) { + if ([self.collectionView.indexPathsForVisibleItems containsObject:lastCellIndexPath]) { [self scrollToBottomAnimated:YES]; } else { [self showJumpToBottomWithMessage:AMLocalizedString(@"newMessages", @"Label in a button that allows to jump to the latest message")]; @@ -2135,8 +2338,6 @@ - (void)onMessageReceived:(MEGAChatSdk *)api message:(MEGAChatMessage *)message } }); - [[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:message.messageId]; - [self loadNodesFromMessage:message atTheBeginning:YES]; break; } @@ -2186,7 +2387,7 @@ - (void)onMessageLoaded:(MEGAChatSdk *)api message:(MEGAChatMessage *)message { [self loadNodesFromMessage:message atTheBeginning:NO]; - if (!self.areAllMessagesSeen && message.userHandle != [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]) { + if (!self.areAllMessagesSeen && message.userHandle != [MEGASdkManager sharedMEGAChatSdk].myUserHandle) { if ([[MEGASdkManager sharedMEGAChatSdk] lastChatMessageSeenForChat:self.chatRoom.chatId].messageId != message.messageId) { if (!self.isFirstLoad || self.unreadMessages >= 0) { if ([[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:message.messageId]) { @@ -2211,10 +2412,10 @@ - (void)onMessageLoaded:(MEGAChatSdk *)api message:(MEGAChatMessage *)message { } else { self.isFirstLoad = NO; MEGAChatMessage *lastMessage = self.messages.lastObject; - if ([[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:lastMessage.messageId]) { + if (lastMessage && [[MEGASdkManager sharedMEGAChatSdk] setMessageSeenForChat:self.chatRoom.chatId messageId:lastMessage.messageId]) { self.areAllMessagesSeen = YES; } else { - MEGALogError(@"setMessageSeenForChat failed: The chatid is invalid or the message is older than last-seen-by-us message."); + MEGALogError(@"setMessageSeenForChat failed: There is no message, the chatid is invalid or the message is older than last-seen-by-us message."); } if (self.unreadMessages < 0) { self.unreadMessages = 0; @@ -2289,7 +2490,7 @@ - (void)onMessageUpdate:(MEGAChatSdk *)api message:(MEGAChatMessage *)message { NSUInteger unreads = [message.senderId isEqualToString:self.senderId] ? 0 : self.unreadMessages + 1; [self updateUnreadMessagesLabel:unreads]; - if ([[MEGASdkManager sharedMEGAChatSdk] myUserHandle] == message.userHandle) { + if ([MEGASdkManager sharedMEGAChatSdk].myUserHandle == message.userHandle) { [self scrollToBottomAnimated:YES]; } @@ -2372,12 +2573,12 @@ - (void)onChatRoomUpdate:(MEGAChatSdk *)api chat:(MEGAChatRoom *)chat { [self customNavigationBarLabel]; [self updateToolbarPlaceHolder]; - [self.collectionView performBatchUpdates:^{ - [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; - [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; - } completion:^(BOOL finished) { - [self scrollToBottomAnimated:YES]; - }]; + if (self.collectionView.indexPathsForVisibleItems.count > 0) { + [self.collectionView performBatchUpdates:^{ + [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; + [self.collectionView reloadItemsAtIndexPaths:self.collectionView.indexPathsForVisibleItems]; + } completion:nil]; + } break; } @@ -2398,7 +2599,7 @@ - (void)onChatRoomUpdate:(MEGAChatSdk *)api chat:(MEGAChatRoom *)chat { [self setTypingIndicator]; NSIndexPath *lastCell = [NSIndexPath indexPathForItem:([self.collectionView numberOfItemsInSection:0] - 1) inSection:0]; - if ([[self.collectionView indexPathsForVisibleItems] containsObject:lastCell]) { + if ([self.collectionView.indexPathsForVisibleItems containsObject:lastCell]) { [self scrollToBottomAnimated:YES]; } @@ -2452,7 +2653,32 @@ - (void)onChatOnlineStatusUpdate:(MEGAChatSdk *)api userHandle:(uint64_t)userHan - (void)onChatConnectionStateUpdate:(MEGAChatSdk *)api chatId:(uint64_t)chatId newState:(int)newState { if (chatId == self.chatRoom.chatId) { [self customNavigationBarLabel]; - [self rightBarButtonItems]; + [self createRightBarButtonItems]; + + if (self.loadMessagesLater && newState == MEGAChatConnectionOnline) { + self.loadMessagesLater = NO; + self.isFirstLoad = YES; + [self loadMessages]; + } + } +} + +- (void)onChatPresenceLastGreen:(MEGAChatSdk *)api userHandle:(uint64_t)userHandle lastGreen:(NSInteger)lastGreen { + if (self.chatRoom.isGroup) { + return; + } else { + if ([self.chatRoom peerHandleAtIndex:0] == userHandle) { + MEGAChatStatus chatStatus = [[MEGASdkManager sharedMEGAChatSdk] userOnlineStatus:[self.chatRoom peerHandleAtIndex:0]]; + if (chatStatus < MEGAChatStatusOnline) { + if (@available(iOS 11.0, *)) { + self.navigationSubtitleLabel.text = [NSString mnz_lastGreenStringFromMinutes:lastGreen]; + } else { + UILabel *label = [Helper customNavigationBarLabelWithTitle:self.chatRoom.title subtitle:[NSString mnz_lastGreenStringFromMinutes:lastGreen]]; + + self.navigationTitleLabel.attributedText = label.attributedText; + } + } + } } } diff --git a/iMEGA/Chat/Model/MEGAAttachmentMediaItem.m b/iMEGA/Chat/Model/MEGAAttachmentMediaItem.m index cdb8eb19d9..045c975a8a 100644 --- a/iMEGA/Chat/Model/MEGAAttachmentMediaItem.m +++ b/iMEGA/Chat/Model/MEGAAttachmentMediaItem.m @@ -91,7 +91,7 @@ - (UIView *)mediaView { totalSize += [[[self.message.nodeList nodeAtIndex:i] size] unsignedIntegerValue]; } size = [NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleMemory]; - UIImage *avatar = [UIImage imageForName:[NSString stringWithFormat:@"%lu", totalNodes] size:contactView.avatarImage.frame.size backgroundColor:[UIColor mnz_gray999999] textColor:[UIColor whiteColor] font:[UIFont mnz_SFUIRegularWithSize:(contactView.avatarImage.frame.size.width/2.0f)]]; + UIImage *avatar = [UIImage imageForName:[NSString stringWithFormat:@"%tu", totalNodes] size:contactView.avatarImage.frame.size backgroundColor:[UIColor mnz_gray999999] textColor:[UIColor whiteColor] font:[UIFont mnz_SFUIRegularWithSize:(contactView.avatarImage.frame.size.width/2.0f)]]; contactView.avatarImage.image = avatar; } contactView.titleLabel.text = filename; @@ -190,7 +190,7 @@ - (NSString *)mediaDataType { #pragma mark - NSObject - (NSUInteger)hash { - return super.hash ^ self.message.userHandle; + return super.hash ^ (NSUInteger)self.message.userHandle; } - (NSString *)description { diff --git a/iMEGA/Chat/Model/MEGACallEndedMediaItem.m b/iMEGA/Chat/Model/MEGACallEndedMediaItem.m index 6161f5da6c..94bfa01b68 100644 --- a/iMEGA/Chat/Model/MEGACallEndedMediaItem.m +++ b/iMEGA/Chat/Model/MEGACallEndedMediaItem.m @@ -82,7 +82,7 @@ - (NSString *)mediaDataType { #pragma mark - NSObject - (NSUInteger)hash { - return super.hash ^ self.message.userHandle; + return super.hash ^ (NSUInteger)self.message.userHandle; } - (NSString *)description { diff --git a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m index f69f655536..30fbf289a4 100644 --- a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m +++ b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m @@ -137,7 +137,8 @@ - (NSString *)text { NSString *text; uint64_t myHandle = [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]; - CGFloat fontSize = 15.0f; + UIFont *textFontRegular = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; + UIFont *textFontMedium = [[UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] fontWithWeight:UIFontWeightMedium]; if (self.isDeleted) { text = AMLocalizedString(@"thisMessageHasBeenDeleted", @"A log message in a chat to indicate that the message has been deleted by the user."); @@ -192,17 +193,17 @@ - (NSString *)text { wasRemovedFromTheGroupChatBy = [wasRemovedFromTheGroupChatBy stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; text = wasRemovedFromTheGroupChatBy; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:wasRemovedFromTheGroupChatBy attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[wasRemovedFromTheGroupChatBy rangeOfString:fullNameReceiveAction]]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[wasRemovedFromTheGroupChatBy rangeOfString:fullNameDidAction]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:wasRemovedFromTheGroupChatBy attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[wasRemovedFromTheGroupChatBy rangeOfString:fullNameReceiveAction]]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[wasRemovedFromTheGroupChatBy rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; } else { NSString *leftTheGroupChat = AMLocalizedString(@"leftTheGroupChat", @"A log message in the chat conversation to tell the reader that a participant [A] left the group chat. For example: Alice left the group chat."); leftTheGroupChat = [leftTheGroupChat stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameReceiveAction]; text = leftTheGroupChat; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:leftTheGroupChat attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[leftTheGroupChat rangeOfString:fullNameReceiveAction]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:leftTheGroupChat attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[leftTheGroupChat rangeOfString:fullNameReceiveAction]]; self.attributedText = mutableAttributedString; } break; @@ -214,9 +215,9 @@ - (NSString *)text { joinedTheGroupChatByInvitationFrom = [joinedTheGroupChatByInvitationFrom stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; text = joinedTheGroupChatByInvitationFrom; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:joinedTheGroupChatByInvitationFrom attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[joinedTheGroupChatByInvitationFrom rangeOfString:fullNameReceiveAction]]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[joinedTheGroupChatByInvitationFrom rangeOfString:fullNameDidAction]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:joinedTheGroupChatByInvitationFrom attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[joinedTheGroupChatByInvitationFrom rangeOfString:fullNameReceiveAction]]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[joinedTheGroupChatByInvitationFrom rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; break; } @@ -231,8 +232,8 @@ - (NSString *)text { clearedTheChatHistory = [clearedTheChatHistory stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameDidAction]; text = clearedTheChatHistory; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:clearedTheChatHistory attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[clearedTheChatHistory rangeOfString:fullNameDidAction]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:clearedTheChatHistory attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[clearedTheChatHistory rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; break; } @@ -261,10 +262,10 @@ - (NSString *)text { wasChangedToBy = [wasChangedToBy stringByReplacingOccurrencesOfString:@"[C]" withString:fullNameDidAction]; text = wasChangedToBy; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:wasChangedToBy attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[wasChangedToBy rangeOfString:fullNameReceiveAction]]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[wasChangedToBy rangeOfString:privilige]]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[wasChangedToBy rangeOfString:fullNameDidAction]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:wasChangedToBy attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[wasChangedToBy rangeOfString:fullNameReceiveAction]]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[wasChangedToBy rangeOfString:privilige]]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[wasChangedToBy rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; break; } @@ -275,9 +276,9 @@ - (NSString *)text { changedGroupChatNameTo = [changedGroupChatNameTo stringByReplacingOccurrencesOfString:@"[B]" withString:(self.content ? self.content : @" ")]; text = changedGroupChatNameTo; - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:changedGroupChatNameTo attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularWithSize:fontSize], NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; - [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[changedGroupChatNameTo rangeOfString:fullNameDidAction]]; - if (self.content) [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont mnz_SFUIMediumWithSize:fontSize] range:[changedGroupChatNameTo rangeOfString:self.content]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:changedGroupChatNameTo attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:[UIColor mnz_black333333]}]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[changedGroupChatNameTo rangeOfString:fullNameDidAction]]; + if (self.content) [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[changedGroupChatNameTo rangeOfString:self.content]]; self.attributedText = mutableAttributedString; break; } @@ -292,15 +293,17 @@ - (NSString *)text { text = @"MEGAChatMessageTypeAttachment"; } else if (self.type == MEGAChatMessageTypeRevokeAttachment) { text = @"MEGAChatMessageTypeRevokeAttachment"; + } else if (self.type == MEGAChatMessageTypeContainsMeta && self.containsMeta.type == MEGAChatContainsMetaTypeInvalid) { + text = @"Message contains invalid meta"; } else { UIColor *textColor = self.userHandle == myHandle ? [UIColor whiteColor] : [UIColor mnz_black333333]; self.attributedText = [NSAttributedString mnz_attributedStringFromMessage:self.content - font:[UIFont mnz_SFUIRegularWithSize:fontSize] + font:textFontRegular color:textColor]; if (self.isEdited && self.type != MEGAChatMessageTypeContainsMeta) { - NSAttributedString *edited = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@", AMLocalizedString(@"edited", @"A log message in a chat to indicate that the message has been edited by the user.")] attributes:@{NSFontAttributeName:[UIFont mnz_SFUIRegularItalicWithSize:12.0f], NSForegroundColorAttributeName:textColor}]; + NSAttributedString *edited = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@", AMLocalizedString(@"edited", @"A log message in a chat to indicate that the message has been edited by the user.")] attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1].italic, NSForegroundColorAttributeName:textColor}]; NSMutableAttributedString *attributedText = [self.attributedText mutableCopy]; [attributedText appendAttributedString:edited]; self.attributedText = attributedText; @@ -364,7 +367,7 @@ - (NSUInteger)messageHash { #pragma mark - NSObject - (NSUInteger)hash { - NSUInteger contentHash = self.type == MEGAChatMessageTypeAttachment ? [self.nodeList nodeAtIndex:0].handle : self.content.hash ^ self.node.hash; + NSUInteger contentHash = self.type == MEGAChatMessageTypeAttachment ? (NSUInteger)[self.nodeList nodeAtIndex:0].handle : self.content.hash ^ self.node.hash; NSUInteger metaHash = self.type == MEGAChatMessageTypeContainsMeta ? self.containsMeta.type : MEGAChatContainsMetaTypeInvalid; return self.senderId.hash ^ self.date.hash ^ contentHash ^ self.warningDialog ^ metaHash ^ self.localPreview; } diff --git a/iMEGA/Chat/Model/MEGADialogMediaItem.m b/iMEGA/Chat/Model/MEGADialogMediaItem.m index 0f409c2c73..054eaeef1d 100644 --- a/iMEGA/Chat/Model/MEGADialogMediaItem.m +++ b/iMEGA/Chat/Model/MEGADialogMediaItem.m @@ -244,7 +244,7 @@ - (void)dialogView:(MEGAMessageDialogView *)dialogView chosedOption:(MEGAMessage #pragma mark - NSObject - (NSUInteger)hash { - return super.hash ^ self.message.userHandle ^ self.message.warningDialog; + return super.hash ^ (NSUInteger)self.message.userHandle ^ self.message.warningDialog; } - (NSString *)description { diff --git a/iMEGA/Chat/Model/MEGARichPreviewMediaItem.m b/iMEGA/Chat/Model/MEGARichPreviewMediaItem.m index 394b64882d..4bcdaae80c 100644 --- a/iMEGA/Chat/Model/MEGARichPreviewMediaItem.m +++ b/iMEGA/Chat/Model/MEGARichPreviewMediaItem.m @@ -189,7 +189,7 @@ - (id)mediaData { #pragma mark - NSObject - (NSUInteger)hash { - return super.hash ^ self.message.userHandle; + return super.hash ^ (NSUInteger)self.message.userHandle; } - (NSString *)description { diff --git a/iMEGA/Chat/SendToViewController.m b/iMEGA/Chat/SendToViewController.m index e191680820..106ae0e527 100644 --- a/iMEGA/Chat/SendToViewController.m +++ b/iMEGA/Chat/SendToViewController.m @@ -106,11 +106,15 @@ - (void)viewDidLoad { } - (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } - (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; } diff --git a/iMEGA/Cloud drive/Activities/GetLinkActivity.m b/iMEGA/Cloud drive/Activities/GetLinkActivity.m index 85501f2acd..6e321919cc 100644 --- a/iMEGA/Cloud drive/Activities/GetLinkActivity.m +++ b/iMEGA/Cloud drive/Activities/GetLinkActivity.m @@ -58,7 +58,7 @@ - (BOOL)canPerformWithActivityItems:(NSArray *)activityItems { - (void)performActivity { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - [CopyrightWarningViewController presentGetLinkViewControllerForNodes:self.nodes inViewController:UIApplication.mnz_visibleViewController]; + [CopyrightWarningViewController presentGetLinkViewControllerForNodes:self.nodes inViewController:UIApplication.mnz_presentingViewController]; } } diff --git a/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.h b/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.h index 8d197eb02c..79c70328b0 100644 --- a/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.h +++ b/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.h @@ -2,6 +2,6 @@ @interface SaveToCameraRollActivity : UIActivity -- (instancetype)initWithNode:(MEGANode *)node; +- (instancetype)initWithNode:(MEGANode *)node api:(MEGASdk *)api; @end diff --git a/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.m b/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.m index b62c9d2a4b..e24f6c21af 100644 --- a/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.m +++ b/iMEGA/Cloud drive/Activities/SaveToCameraRollActivity.m @@ -9,19 +9,21 @@ #import "NSFileManager+MNZCategory.h" #import "NSString+MNZCategory.h" -@interface SaveToCameraRollActivity () +@interface SaveToCameraRollActivity () @property (nonatomic, strong) MEGANode *node; +@property (nonatomic) MEGASdk *api; @end @implementation SaveToCameraRollActivity -- (instancetype)initWithNode:(MEGANode *)node { +- (instancetype)initWithNode:(MEGANode *)node api:(MEGASdk *)api { self = [super init]; if (self) { _node = node; + _api = api; } return self; } @@ -43,27 +45,11 @@ - (BOOL)canPerformWithActivityItems:(NSArray *)activityItems { } - (void)performActivity { - NSString *temporaryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:self.node.base64Handle] stringByAppendingPathComponent:self.node.name]; - NSString *temporaryFingerprint = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:temporaryPath]; - if ([temporaryFingerprint isEqualToString:[[MEGASdkManager sharedMEGASdk] fingerprintForNode:self.node]]) { - [self.node mnz_copyToGalleryFromTemporaryPath:temporaryPath]; - } else if ([MEGAReachabilityManager isReachableHUDIfNot]) { - NSString *downloadsDirectory = [[NSFileManager defaultManager] downloadsDirectory]; - downloadsDirectory = downloadsDirectory.mnz_relativeLocalPath; - NSString *offlineNameString = [[MEGASdkManager sharedMEGASdkFolder] escapeFsIncompatible:self.node.name]; - NSString *localPath = [downloadsDirectory stringByAppendingPathComponent:offlineNameString]; - [[MEGASdkManager sharedMEGASdk] startDownloadNode:self.node localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp] delegate:self]; - } + [self.node mnz_saveToPhotosWithApi:self.api]; } + (UIActivityCategory)activityCategory { return UIActivityCategoryAction; } -#pragma mark - MEGATransferDelegate - -- (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:NSLocalizedString(@"downloadStarted", @"Message shown when a download starts")]; -} - @end diff --git a/iMEGA/Cloud drive/BrowserViewController.m b/iMEGA/Cloud drive/BrowserViewController.m index 8cc1870714..715c8207c8 100644 --- a/iMEGA/Cloud drive/BrowserViewController.m +++ b/iMEGA/Cloud drive/BrowserViewController.m @@ -13,6 +13,7 @@ #import "NSString+MNZCategory.h" #import "UIAlertAction+MNZCategory.h" #import "UIImageView+MNZCategory.h" +#import "UITextField+MNZCategory.h" #import "NodeTableViewCell.h" @@ -431,12 +432,13 @@ - (void)attachNodes { [self dismiss]; } -- (void)alertControllerShouldEnableDefaultButtonForTextField:(UITextField *)sender { - UIAlertController *addContactFromEmailAlertController = (UIAlertController *)self.presentedViewController; - if (addContactFromEmailAlertController) { - UITextField *textField = addContactFromEmailAlertController.textFields.firstObject; - UIAlertAction *rightButtonAction = addContactFromEmailAlertController.actions.lastObject; - rightButtonAction.enabled = (textField.text.length > 0); +- (void)newFolderAlertTextFieldDidChange:(UITextField *)textField { + UIAlertController *alertController = (UIAlertController *)self.presentedViewController; + if (alertController) { + BOOL containsInvalidChars = textField.text.mnz_containsInvalidChars; + textField.textColor = containsInvalidChars ? UIColor.mnz_redMain : UIColor.darkTextColor; + UIAlertAction *rightButtonAction = alertController.actions.lastObject; + rightButtonAction.enabled = !textField.text.mnz_isEmpty && !containsInvalidChars; } } @@ -499,7 +501,10 @@ - (IBAction)newFolder:(UIBarButtonItem *)sender { [newFolderAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = AMLocalizedString(@"newFolderMessage", @"Hint text shown on the create folder alert."); - [textField addTarget:self action:@selector(alertControllerShouldEnableDefaultButtonForTextField:) forControlEvents:UIControlEventEditingChanged]; + [textField addTarget:self action:@selector(newFolderAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + return (!textField.text.mnz_isEmpty && !textField.text.mnz_containsInvalidChars); + }; }]; [newFolderAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; @@ -507,7 +512,7 @@ - (IBAction)newFolder:(UIBarButtonItem *)sender { UIAlertAction *createFolderAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"createFolderButton", @"Title button for the create folder alert.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { UITextField *textField = [[newFolderAlertController textFields] firstObject]; - MEGANodeList *childrenNodeList = [[MEGASdkManager sharedMEGASdk] nodeListSearchForNode:self.parentNode searchString:textField.text]; + MEGANodeList *childrenNodeList = [[MEGASdkManager sharedMEGASdk] nodeListSearchForNode:self.parentNode searchString:textField.text recursive:NO]; if ([childrenNodeList mnz_existsFolderWithName:textField.text]) { [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"There is already a folder with the same name", @"A tooltip message which is shown when a folder name is duplicated during renaming or creation.")]; } else { @@ -528,14 +533,8 @@ - (IBAction)newFolder:(UIBarButtonItem *)sender { - (IBAction)cancel:(UIBarButtonItem *)sender { if (self.browserAction == BrowserActionOpenIn) { - NSError *error = nil; NSString *inboxDirectory = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Inbox"]; - for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:inboxDirectory error:&error]) { - error = nil; - if ([[NSFileManager defaultManager] removeItemAtPath:[inboxDirectory stringByAppendingPathComponent:file] error:&error]) { - MEGALogError(@"Remove item at path failed with error: %@", error); - } - } + [NSFileManager.defaultManager mnz_removeFolderContentsAtPath:inboxDirectory]; } [self dismiss]; @@ -621,14 +620,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } } - if ([node hasThumbnail]) { - cell.nodeHandle = [node handle]; + if (node.hasThumbnail) { [Helper thumbnailForNode:node api:[MEGASdkManager sharedMEGASdk] cell:cell]; } else { [cell.thumbnailImageView mnz_imageForNode:node]; } - cell.nameLabel.text = [node name]; + cell.nameLabel.text = node.name; + + cell.node = node; if (self.browserSegmentedControl.selectedSegmentIndex == 0) { if (node.isFile) { @@ -894,8 +894,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } case MEGARequestTypeGetAttrFile: { - for (NodeTableViewCell *nodeTableViewCell in [self.tableView visibleCells]) { - if ([request nodeHandle] == [nodeTableViewCell nodeHandle]) { + for (NodeTableViewCell *nodeTableViewCell in self.tableView.visibleCells) { + if (request.nodeHandle == nodeTableViewCell.node.handle) { MEGANode *node = [api nodeForHandle:request.nodeHandle]; [Helper setThumbnailForNode:node api:api cell:nodeTableViewCell reindexNode:YES]; } diff --git a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h new file mode 100644 index 0000000000..b7b92681b7 --- /dev/null +++ b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h @@ -0,0 +1,21 @@ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NodeCollectionViewCell : UICollectionViewCell + +@property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView; +@property (weak, nonatomic) IBOutlet UIImageView *thumbnailIconView; +@property (weak, nonatomic) IBOutlet UILabel *nameLabel; +@property (weak, nonatomic) IBOutlet UIButton *moreButton; +@property (weak, nonatomic) IBOutlet UIImageView *selectImageView; + +@property (weak, nonatomic) IBOutlet UIImageView *thumbnailPlayImageView; + +- (void)configureCellForNode:(MEGANode *)node; +- (void)selectCell:(BOOL)selected; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m new file mode 100644 index 0000000000..0de6526a3f --- /dev/null +++ b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m @@ -0,0 +1,43 @@ + +#import "NodeCollectionViewCell.h" + +#import "Helper.h" +#import "MEGAGetThumbnailRequestDelegate.h" +#import "MEGASdkManager.h" +#import "UIImageView+MNZCategory.h" + +@implementation NodeCollectionViewCell + +- (void)configureCellForNode:(MEGANode *)node { + if (node.hasThumbnail) { + NSString *thumbnailFilePath = [Helper pathForNode:node inSharedSandboxCacheDirectory:@"thumbnailsV3"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]) { + self.thumbnailImageView.image = [UIImage imageWithContentsOfFile:thumbnailFilePath]; + } else { + MEGAGetThumbnailRequestDelegate *getThumbnailRequestDelegate = [[MEGAGetThumbnailRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { + self.thumbnailImageView.image = [UIImage imageWithContentsOfFile:request.file]; + }]; + [[MEGASdkManager sharedMEGASdk] getThumbnailNode:node destinationFilePath:thumbnailFilePath delegate:getThumbnailRequestDelegate]; + [self.thumbnailImageView mnz_imageForNode:node]; + } + self.thumbnailImageView.hidden = NO; + self.thumbnailIconView.hidden = YES; + } else { + self.thumbnailIconView.hidden = NO; + [self.thumbnailIconView mnz_imageForNode:node]; + self.thumbnailImageView.hidden = YES; + } + + self.nameLabel.text = node.name; + + if (@available(iOS 11.0, *)) { + self.thumbnailImageView.accessibilityIgnoresInvertColors = YES; + self.thumbnailPlayImageView.accessibilityIgnoresInvertColors = YES; + } +} + +- (void)selectCell:(BOOL)selected { + self.selectImageView.image = selected ? [UIImage imageNamed:@"thumbnail_selected"] :[UIImage imageNamed:@"checkBoxUnselected"]; +} + +@end diff --git a/iMEGA/Cloud drive/Cells/NodeTableViewCell.h b/iMEGA/Cloud drive/Cells/NodeTableViewCell.h index e0991cc951..d602ee6e09 100644 --- a/iMEGA/Cloud drive/Cells/NodeTableViewCell.h +++ b/iMEGA/Cloud drive/Cells/NodeTableViewCell.h @@ -21,7 +21,6 @@ @property (weak, nonatomic) IBOutlet UIProgressView *downloadProgressView; @property (weak, nonatomic) IBOutlet UIImageView *versionedImageView; -@property (nonatomic) uint64_t nodeHandle; @property (strong, nonatomic) MEGANode *node; - (void)configureCellForNode:(MEGANode *)node delegate:(id)delegate api:(MEGASdk *)api; diff --git a/iMEGA/Cloud drive/Cells/NodeTableViewCell.m b/iMEGA/Cloud drive/Cells/NodeTableViewCell.m index 828dcc493d..d94c145f52 100644 --- a/iMEGA/Cloud drive/Cells/NodeTableViewCell.m +++ b/iMEGA/Cloud drive/Cells/NodeTableViewCell.m @@ -40,7 +40,6 @@ - (void)setEditing:(BOOL)editing animated:(BOOL)animated { - (void)configureCellForNode:(MEGANode *)node delegate:(id)delegate api:(MEGASdk *)api { self.node = node; - self.nodeHandle = node.handle; BOOL isDownloaded = NO; if ([[Helper downloadingNodes] objectForKey:node.base64Handle]) { @@ -48,7 +47,7 @@ - (void)configureCellForNode:(MEGANode *)node delegate:(id - + - - + @@ -968,7 +967,7 @@ + + + + + + + + + + - + - + - + - + - + - - + + - + - - + + - + - - + + - - + - + - - + + @@ -1413,41 +1522,41 @@ - - - - - - - + + + + + + - - - - - + + + + - + - - - - - - - - - - - + + + + + + + + + + + - + - + - + - + - - + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - + - - - - - - - - + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - - - + + + + - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + - + @@ -1907,13 +2083,13 @@ You are strictly prohibited from using the MEGA cloud service to infringe copyri DRVI>v4{|HDOu|O@A7vGy0{Ws T>kY<8Wx2doS3+ncTUdMUc0ojyeqHleXN|dqSs@;c;Deuxxb$MPFeM&h4V3I zxydXYseDhHz{09*vI$p8QV delta 1614 zcmaiudpOez7{^0obEy=H(vrI#?eg0NwOn(lkxNr8m(504?j~LQN@_C))x=Sr3dy}( znn}xL?j$vqOVNd6Wl2u$SkAGI`tRtE^FH4{-uLsq-}m`08pus99|TY2^sxb~`o#t5 z#(*`2vg9LTi2RVj?d=DGv}Ab;%rh>GgoRXf$J|3XRR7FONvLdn@}*7gnxKUF-E+yw z;5oSW)A-(G$oJG^RaHZR+E*qf-3V+?T_vM9EJCW|T|Xqv*C8c5%oGVGmk77$>Ll(Z z-^xm(vaIc8#W4lD!ODL`H+dmN zLqATEWiTF8Ht}(wiLu01yi*-;J7E^ft=3iBSbNt3N8zlTxcV+XO;LWJB)DNk_r8fv{VrEw6r|<}nWx znG14_&h>npxW-?V5?U~IpB8bZg75%P&8E!0bB@TnJI+1J4p)Zo`*gjdB%?f*T2}`# z-|?W$tVhmc&{SXDy$PBn+G07Jc%;}Z{46))LGGE$tZzau&7oO>+rgPUUfm9xbqOOr zL}*)A=vvnYR~K5~cDAwEG1Fg)Qx3U}0;hL$*BZ_W%XO`G*AOw;~u~_`RhbW;eJ2%(Z%&< z_~A;A)vwJULYc|rKNqlFc{2CD18;0VJFnYGE`phuH6v|eEo)1i})9Wx1%f>QhOv&_$sHEHF=!|hJ-v0WOeL6r#kHV>d0Ly3RT6^ zyJiEZ)GPZ;&+Q$hvrEk;qF;ChShTz2s*zxBtJj>NlfT-%iM!!S_kA=g^@sD?>mNoA zlzzfSZ5=;4qqDJmI^FgkO5tE50mD;OJs!QSQKdx;xj-u}VEBo{T01q$GgDO}Os!N% z60bRFdg)n-+mKbO(|(@oT}Z=*o2$4ZqjN@jrY}@P0o4vc-rH@xxCgSLIo$e*kyBuV z7GZm6ed-D2vDHf5#P|E2HrBW*H;JZB-Xj$eoyFW|3~N8#jCT-5?-47e~6PTTN z22au2v-!NpywtudA0rIdTH1Bb!jehkWhiMhkHsDo((NfJuQCiVcH4Ggdi79dS!+^R zUJa{i3hA*O!nxZS!O~X<~^@+tQ>nq(!m@fTO3hf zfgTl888NXTg$oyFY{O!z-!$i$RPzgoX&_3diFA)Ha;v_xyUf(6 z{AI7(*yccbas!Fi9``L{aH%aD!k0svm*m$3la|L>U$OE;sZ_OjvBc^YU;d%bnY)*f zRJH4MmSo`bWT21Uj%v{~@;GwgNK%<6{9imRcNN^Y&hC(jOA6j=x!C6iD z=fG)KC$A%rgJGUNu){C}+-g~9j+=&s8X9g5M$v^E|)n`rg-l&Hb78=iKK$pL5P1uOp_Xp(O>EMu5edX`g8G zxt|}tYHkH10T@8SIe^cf2cWt{7hAF&AWN4F0H~I|6Pf5re>!2wL=7UIL?8l6N?{3$M!F{`-I+wJu9%#f`2*(ke|9-C~ zk)oFHY!AiO(`f}~C&C_Q>WBtw03(wIj12ws$JqasSv^-0-jGNJ z%;-)vv;YeLs!nnuxf))>;)wtw_3CbLK>nwL-+Y|;&BxE`D15hqUZMvCRb^Djg$Aa0Q36OUeNgT96)Z6wl>W4T!N^JvOWo5|SFOy%H^?Xu5c z4U^v2Ku{yunAsnhVa&TU-(Y_5P-pWx%V8d7E@S2w<2E)R)P@F2-&cxaa&n$_Yx&xe zv_GMCSh8LZ;x@4B7b_yKF1nWqtl2Mj-WMW3V@WV9ZxNK)1-)ENl#mI5%kXD#WEP1` zHkUfB<4hlo#at;jMv!{J% zx7k`LH{8U*PbV~89RJfxm%3W9?+#4Bzm#-WlUdBfIoU3Du~>L<(PUuSZnJ)9?3l0N zj$*s__LUXL0yRxFGRlykl#)ZB)zN;QCLszT-r7|mm{DY@CTX?LDZz>y{kw_@@MR?w?Lv$c~HnbiMa6?IYeX7QOfz>;c$CkQ9%XojA**eZ? z9_})(t14nRy|yo!;3}3WDVQ}DtHR$fd)#)o#?%+!{R(s`Q104ZbI{cn`>%2C^GB(GLmDo?m`4Nb z*#kfF@MWqN#PDZxC#wzc95H3Tp{}ZThL^QPJuHSNPG$2IB=7L4+XWfwdWx%mkcDd4 zEpBf;WP1f5_wF$J1Rc1?(u>{yhUuCAP}w=SKGqCp# z^S264hMGi<6Xt4rAvhs?&9~~Xxr~c!38<2LFtXNvzG?qBWX0?GsZrkLK<);&kGNYW z>cD=&e#ge5kGIx~$8eZ}90R#e+yz9PL@)CQvCpvY1RZEZ>IdXu$RZ&;H=50H5|8h^ zQGJdUhFps0MTdwA7>G(FR|H%}JBV076d{xo@f~LgDA&U#DwdcM(R9E{j5|(QFX1GF z7o#JrsWx;;L8n^fhRO}~M+W=zT>6X)?$xB4VkjCuIzCWTJR<3H(qu<*d||9(;(XFg zF-*c>l4p{Ybh^0J2|t~0IbW+-^cz$f3RmH`Mvu}xSVMkH&+1tJGvn{K~KTuXVh+;%D z+DV!)O^817j8?o{Tg^c2o z#ayW#rRbmC0T@PPTnlyuyg{7k7&`}*1 z4d3*QUbe2GR#5jjPTN6{g|fA6nPu-xOp3Gy9x61QZ@-mwSyxCVPyXE9T0!n%*|2I9lL++Cm&A@#Wn)Bhycd^QFc->KA>^rC5}W@Yi0d zrLJ+UiEKl*;cSYbUZE7O^*3aXleIxU{Riehj8}E9L{74a-xPn`i_RTOHR+b<-rt?Y zxliDuK%dlM*PiR+Rl=^Z(gf*9E3a!AeOHDPCr#(Kx-Jez&3H6BH;=qg9NMYcKFxiB zI~EnumXI4iBShu2{4=TaYV6h0%D8phvcqz&(pjZ2r6{FCbslwvKF|&94$F?m*2w3< zk270?+ZfOpP!Y!ma5hK_WW=V#DZ_Dak2|QTfw`eJ@Hq6fm8f|iKa1K|b&ufl9J2c? z__o{*OG_e)APYCDu6X%+d4i;Hni}fOwc63`uiH@(QT!M9J+(SeEt>tPi<&nzlQb_R z8B3a!PiaM(MglpC{Kt>*-4yk3&0l{#av^kbVFEn4B#kh?TfXqt^(igClUfpE)nIb{ z)UhMSkB;%|h8=3mr|FigT2*_}K!fqMq1$mYt*2+ARxCD%Ut>W@K~I8uG?JPv!tm%4 zG#`4B1*7`(z55fZPXSZ;2{8#-5{Ues{QuYoRcuuBs)=F4%T8M$6vZjfCS(>&z2>ET z|I(F1nJPW8x+mRCY-;b_`KJ3tJ5uz%*liQea)&3QwkTryOZ~0`)2mY{Q!brHJ3e=8 z`^wtLdb-=zd3+1`+912+$WZV1iGsyBpHv^pV#rR^z_Hr2yH*Rn_Onlyohh@|%6INg z7T>Ac<1@2mu?-zvtMQ@T*-bsd8nJh4NxJUluH`$$S)b9J9=3dLOXV)_Gdp%W_=U+# zaWLjoT7h!$PXETBO+ICH;o+b)wfs-p=#VOP=x*X>DW+);HJ^G~t3qoIRg9uWZbpuH zKVEJ5*cqABI~iOr@$K1qVozbZuje+!7T3}I(XnSfSy^W^uTEiWV9j}k@L{Ug(|$Q8 zH+;0PPI)b0Cu1AG+7>&zfZ4z#rbj7z-s1TB$qV9aGd;E*^m+T={LG-x@6apF$9bO zI7}Ljkd_7X-LbA@Z-7qd-=W@%Eb%LD_^;vMq{dkp82k)OUIq!5MIdC(n8IM<^!qQ% z-{PTDKfxVOpP{~glRi?Z(?=(=J;_Cbf$ZOX(#4A;!tek8iqy-MXaj};a0D3ke-|Jp zD~pf?Y=EB{0*Rn!fN=pXzciT48G73Pt|4S#^!eoP8Vn{wKh{4q8991s|Dhq|{_PL` z|JWfB^qJ@H?;_yHfBB%Pu)U-x}|{&-&y6D=J%xI6+3X{0aG zr*oGcbT&4FkpK)J;$6X~P61E@f~S*@Gk{{4%mAp4i@OiOi}7~H`4F@S_CyB)prQiy z_VFU%Jiz|UX=ZvxAUJpR>aouxRy&z;#GUG$RrMz$r- zwJJ7Yo?a3{E|M!M)y@`XuS=3Q>Pq71+4*kX6bymDjqx!z`_*h3y(`p~9=`)Bz%#Dwe3G+l& z%cA=em!<&|LpZ*({>C#qr;Y8Xr+df6;P-{wv$(F%)=xl>P~IWgK5p&@W1buyY3=4W z8x@EZGjmiNrisQczY17peF`=I@Rt#o;OX$IaE#GTy?2Vd^F|_IJCi#@g+HWl=E_9^ z05!om{djs2JbeJfPg$4|yor~+>fAbz2Si3uK<6C&gOlY0gHCgS z1C>lB-$Ft4J{HWu$V>}?FVl54d-k?9uCnaoW9G47enoS11Yy>+SbD!vlv7e3>o%9J ze31`!sCqA3Ya->{za2!DP}G!UV*+dUp-)|x5~i~xn3pz*C~Sk?EGNn+M8Fk~YA>E!>q^e)O#Y9Y#-l+|diY2D1IaG* zPp=&}7D~<6@$h2_4TiLv{sz?LGVa?j;oxH8U2SHFrALZ0dA3k|cGj|g!g;-RaOA*s z^DX7qS2xctNX<~w)k*1=BknU#pcEmd6-ldm`%7yQ&oY&Hu&L2$PmJdPro~-MWyANE z4n(-y#x;&|OLN_tnQ`}K&Tx}I_DOLwBb4~2HbQANrR~e+xs#8CRYMNz2_gvE^hddm zPHVNdbMquo9ky2aw#Qy6x%}1IAbUUKmGDCNr?aM?5;()6oU6i5g;?B?cL0Y4_0fX% zp{3bHuBW*PIA6A73Jg~=snBD8O;l#mJj^QTsd|+qaN3#=4t`xz?OaGM_KsD!`h9Za zAeT+FC(S@z!hB+dJJ!Jq@=#VJYb4CQj%CjscAp1eWQaVBMfSpO@nFP7)?t7tELfM1 z^)9QtE{lsA=$X38TPCnN{4KNZ4cCiIy&+dSK}MnIi)=Qa^RISanPo*m%I+2_V_Fm;=N3lUaIjyWcXs2p%jsDb9hZZ~Lg_FL<9__|CY?Hr%d0oNJjPKxuiyb>7|zj(c3pN)bist8ohJ3y)mo^IGplUnevJ~}Kg7s^}bJ&(V3 zs0JJ)9&}(N_F!|Z^c_wskXtD4p}T;jyW|-@QI1KDtuX$2q-n@wV;_kKzAKG3c$ufS z->O&ZiAx#93+P2i3Y$sFq?Co6(Q}o+N-0ZG4#l^gaKL!&vsAM+E|Sau>>#{xswN3X zqy&uh#kDmCjg<6Xs$EgLqM2j1`>|)QMSgN+nw2p{D^NcWiit-gEhUY$7RDEl-4dsh z0wBf-14({KcJdk0c87xWzd!m`!D3pcR%afPRdOy%D z(~r=_>7w+~H)d@0unTJlGm#upJgJHlyM&y0NgboUM;E);Zdc1Bt{?S0dM!~R(J_%Z zF*e`eEU{0ZFD>6A-?$g%D&>lE9dzC7lP`#&=2HFAscCk|?eTAspGzvEu2yWger@=y z{5f7URkTJ_MD(m^60#X3lIoe-no3WdM7ESUTX8)HS<$T~pWDI`Q@*sHafFoKQpzkW zn$4B#Qpp}FP%gB8Tx#KzWmMX+7aJ#Ut*BmZQ*Q33VSE~D0ktbOoy$})%_z*suEo?^ z2fcaK3B0>-OZ{Hm3^WqaO(B7tSk4{k8vsAvbdEdG$dpv zJ1c80H!XY6QpR$Zg-6-vl8uIUjj3)}`@oh6*D?Fjjz>ci&kOV9Mto~$15c)6ONNB1 zjH;+BJS!5LQk!sgwraNp}7hGkedzPaLzF-e@-*h?(?l^k^Jv3ZR5 zG%p!*t0f^feo~amWqUKJ_&oW1ae3S-e$I6+SLK9CluC@s-WuPUf!zx1FR-uMmsIH%;I0Q=BMn ztl)6RDXi6=(gg`}=|9@Y-6Mja9m_ue)DkZp8?l7V^_+#WZR6VGOkcw3DU3q`Ggq5w8^=LqF(Iv;Q2xVysv;mm~)2w{s z()Z=;-iK;kWP>B#mX1})x4#>F)g?*ZgWRy>Ds_D}?1Uj?yfJO(pI9DG9rtY8-@4Sg zc^&15^1JL*J#q{NV&i))C8k;&n`5aI_oA$2d{Xp%AM^F!Q3~Lj+$^ilK>Y&%FCT@l5 z9!4%>yYn_7Zg0yCJ7symhI3}m1KK63o$=(6Q$BGH)`k1)pr(>5|h+7}O zY6k2rm@+|Ry<7twKcuuU+Ol?VE}i?-;FajW{3+?JjFlIiR?$|O{U7?HUSy+P0t^C1 zrh92cKbN^j zxzWP~HL5EiTbY~o%Pr)o8RIqM#EckKziXV|7X76>94AIr!91j2W#}PxEJz#7O zSO9RCJRBj90!%OCynL<#3_||__5MCGzw?IwZVrxUoltw`D0B+PpQf19erGgo?1J|{v#&CXh?MU^ZV~i{k;f|U>E>LfMNf8 z0caFsS-=taWkaBkGmd@d0z7})V2rf>X+t2BN7?<*wFyv zAoV;Qi43v)9BVP;atd#+0K=ml5NJgN3gL)C!4%Q3SLK`2TkKDK~E) UhBST@1fhsl07D?!#ya5t0eR{ro&W#< literal 0 HcmV?d00001 diff --git a/iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/Contents.json b/iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/Contents.json new file mode 100644 index 0000000000..c2e9eceaa2 --- /dev/null +++ b/iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "saveToPhotos.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/saveToPhotos.pdf b/iMEGA/Images.xcassets/Node actions/saveToPhotos.imageset/saveToPhotos.pdf new file mode 100644 index 0000000000000000000000000000000000000000..18c814e0108bd56cfbe118f983112024a000e6b5 GIT binary patch literal 5576 zcma)=cT`i`v&Sh>ARtA0QGyZ>1d>2#N^b$_p!AkN=uLW+jv^pMklv&#RZwcE5fG4o zh=@vwG?6a7$qQb6zk7f8kN3_xD`#i+cTXl~&sy`D4c1Xo5rhee0KwS#AM?wlTlvG- z9v~b91-YRdfl^W+hz8o#&dVMoN-*hxASw>dUT6=((;4Z7Rzh35*`Pr(GC)r+4>ZyR z=trDmq+Z1#PW5Gvubw*bQQL{}5g?3Nro)-Tild|N%D6To_zC<`-d1+bwRly7TJG9K zr+w+53w-OFH%uwYSAF|;s4OK6O@`tOF7(>py}BHco!xQgt5wYuvzv>AxEg<^v=wJP`; zySsl{;1K&9Ycg6~=AFo0u$G6SAi98pA(ETx>OT9g<6(#9OiUx8n7 z{&F(>_5H=+F&e=kWJ5)Gs$Lrg zq_x^e5i7A!_wI|#y$;dKNy4_>)f~_e{i#{?J_l{P*j|p0Us#3CXpDH_OCKAmuh8nWMx0IHBHB~t3r&C5&J$f1l6nfKx4)5 zfjnt-LlQ*wha8Bi-TMZT>j??o7sw@yz`O!~u*+4s>P5YAi!%b)#@!fdMUw$6wsilDihE)d35V2 zqdss7s-kt7kqPQAQC}flEFHKo^1~l*nS4H)tGmW zlSwmigmm;>@7ZPBS*BpxX#8g7Uvk^HT{IFw>Vx1Mp)iHSZPVP^DO(BGZF!z8mZlf) zr+xPzZCQ0?(xfHOEq1RC--Hco{}k_<{&p@tZ?Eayvz*4(akhyYh36#nI#Hh>&W&P& zb6z$!S?M--){?Z^wjwUhtMTBA3c?q1w_(|_Gd2^@ceG+r#9G=!7Akr{p9SB7?-mpH zo!1Vn#X0D(J&@`9WXskoq@aAa6k|Q!+(?IykbRO*KNMCxQn(ivG_nmPw=|>bLa?M* zeaH#O4=&uVUG8|?cNb!ZDsFv{-sE|`F<<3k>r2YW&NO;GM;MRSYyWm?Aj!l`)HBtj zy^C!{D|GR88jIN-3HlCxIYG=?O*E1-SEMjYR?#di0qb2#GjB$>BA3)aOhzO(%GkUT z`TgkVgbJ6si^Z-sftWdT?qD`wcToG&h7qlHh|Gztzdqu(XXXg{OmD9tPVashYUuvm z9orXV_adLfUdA1py&^da9%)mClks0n&`V4K26geel1e=mB9S^`&AXA^DV;KEPJMHG z2y3=CfI4*Nk-DfXoGzEOqB=b1!=LiXv(`by%$|Kpd@o;lsxgAnw}5qfLi*t=zEp%x z_y8;y+KQ!4C2Qfdrb7vD?xeP=*~zAgBS!2Oou<4_nqTQ&rK<_jJG% zNW%&=cR3O5OJL_dY=+VNbVfSDrb*qeA?Oet7~;mM;%4dI3zy3LwiY456`d?n$ z4ap427Rc>bho4G4lN~?a^Ssdp$&X80;EX?}+8$a6SsENmOdytpGmtbd=UtVx|7qtw zbEvh2TQ)4;qh^d+x72%3OzZghwC-Thml1_|evoIWx69$5u%bDzOaE4OJ-ygE+`fJE zxUkxAY})zf#0yO6!F|1rn`Y@Jida*twj_+uckm?|kJ}+}O;rr*w>E>`igC;~WQLGf zPcKi4uuzVwZzUYp>~=5Y5lxLAyOHo}unjJRgkKG@S6s=8ww_Wt5ovj^DcHPOx?mo2 zkyc3U=wdGm|8@kB(mSU^Eo0a7*k?<2D+3$y`Ix@=dWhgxr!6;SPRr~Z*_TV9{p_vc zr^l*B-z?=S-^z*!yxhh>^m@`L>sRTBN2gW~c9uQpp>H%vMNdzO1YwOa422LJYVHvgw~>v*_X>!ZCurUa)-Dj;(ZMA6OJ%|qWEX^jS*6?a8X z7)boLgTMKZ_?wU4^tY0fD#K0w_T5ZmKc@bYs*)bx5Kkd3==4sW8y-8Q9$JAV%E3Vy(er}#mu z)6=P|q&zp!yci?Sp0f>DilK00L>5Pmr)t(oW zc~)?_@^QJw)6Odu5DXL1 zF1Jb6rMkA%J4E#^=jG<{724|{q95mo>Zh`|E=N0CBxAo)@lwXFtT=lT=Q#;o|0aHv z7w*>I9xb_-F|c-IaI=J2HtebzqX=4gzNEB7U1@NTiaHHvbFxQ!I@=-X@b*o&Kz3dS z^JdhyJG$ReC?aF*TcV`Gj1q-xfRQ0%_z)H`UNY8uIZh1r-j+ncQIa~1YGi$G(nN|^ zNx5BR14x3GO=)4kzP48T%A{(~1mS?6i-(+)W)EHQ8uC~47j~%afS7u+eSLJ@Mn=uUMg@l==tdH4n`<5T%(iA+T_844e0*-gm(73Fm# z7)W0$#w5}v%N-2!l(FzfJS$Mtk>0-Q#nw!29&wmYHVlHFKOqi|q)R6mMPB$w^eS|s z<|YR@qHADA$&WFcjJe}8{#GNC40S^nGoF2eNFd6x>oK`pA35oWiV->crjkOU5jQa% zms8S}B9e0D%tY!U_NI8%q`PyZ=kFIF(^;5$SZD7W$Kg>1$PcJn(6MyubOR0M1uZXCU_&ka-%R~s;dBjoaQ0%uPWz$u)D>j{Y4nfo>^ z8~GynNhDntTsN#t+v`d+tv}Wb#aH|2qkO9x2ancc2DNBzW<74cjQX(KYK~XTd8BzT zT#w(}ut9jR7|U5`S8?ZoEWtF%vN|c(co?*AaVRTHXi45`lJl4IS1i`MQ0DsH=vjJm zj)^u#Dfm_}1o2oTZ7Xf|P37a~NlvNDX@Owvl<_p*G)tj8UQ4c!TR%&V8%cCKu-EQe}Y~@h!-7=K3;mg&G`+pO@RiZP3N!8bIh~N*Y)e2?}A$Gi|DZy$32c~iA&gm zFErt1ai7Ji9V|{;`BD$~UHOAjucX?h5~n6S)41a{#xR!i%;lN(d#EFiBhqog@n}rw zc|5KZ=bMYmv4o$_9pC?6(;O4fc<8v({ayO|W427THa1qaJ8Wt29#Pgz*UUGW^O=kA z-gd6UJL7SPm;wZYrA;JR4Jg37AZQo#|KqRHpdmDXi-Ms|f-bwl*#$wH>$ z@(pGU`o0R<>JTG{Wwq{lfsAfmWnNJ`qTMv4zhfBm$tqSptFx@&VL>}G7g>n6zWOZb z`3QfZN#V^N3F}vPA8yLbn)1}dOdIZTsGmIA8+`hv>>oW^{{=ZswHztR24Ba^=GcOOH&_tDGeT1#Z` z7~S$WeB;n&+$^p zfbLG>&X#aa$U94Jv-eCS3df2*QBoA5ROXC_o-9HF@Cu$4|HeCh_xyYTf+%GrMQn>| zLf^YSL~J~hI+L%;8^mknF@&aapmLhBdYX}daowCsoJkz0M4E|{oiUKx$D@4j-K6^c z*_E%r*)<^%vnO>cpFCd9mk;2o5-mH8@A6+}=e#scdm2OERX(p#wQbqtI}aFt+;aaY zd9mmEV*IB0KKeKbkQVtOazrT&YaU~*R;9+MHcO%{|8mCrh2@X1x$>06ltMm{@{#hl z4w3cy^`i=4tynfw5x`pkz%sJPAOK-N`9NiPO74`MDYxDUTecUO!=0sf?^`Xk_bCZ^@ zd1o_zr)@dG1>~YSu`!@B5RYb`c6~VYUN>L2rp?N}ZhfIgp-Dj(j}aU)rFMSp%;>y2 zX z;i?()%K5<`Pk%JD7_PJ#AWiRD(Ksa7B-9I+y};b6c0`PoelPkS#+Hz|fcYNSu$uF! z_w#uBv`fe+aXjg3GMUQ^9IYXJdpb^)@-DBDU+@*(xaFtco7?R|EJKu5RX3Axh$uvH zZ&!l%*vNq+8e+`O zlFakH_d}s4YBM&5`Th+*C*~&+4=Ykgx&5s~cyUoG#k4|&gNn_6+t_igT zo?3j7UJ9N%86hjDv5*}MkT|hFv0j;d$_v!y&wVCac`~*?Zd;DoUdbP~!qq*nONee% zgq)@xRBLyi!=WVjPVJ8Jg*4w>FODo#@+NpW*vc5qS z$3OgdTx=Jn_ae8B{teJ)6#5IA#bCmJfcK1F&uj$9RZ~<{KzgEWKxY7}2QvR7IfLjw zO#C0l_5?vR(KZf91vfvCDU=`)mLS}}AlaKh-Y^hE-ND9_0MBQLPVn|OK!%;k|LIW? z>4kK5v-=C*J%4lif3O?|`@Q0t3({I!A7p^`@N{r<1;L;~FgVEUnu523vkeF)Aul2c zGvfp4dLuo&0zd?6{{{MfUVMN43+zwBa82okFqF_qND7O?#G&FMA|_BMFX8@=@^3K` zIsqGRYr@IhuQ&(?d5VMsJ1;^2N@p1V%O}C8>1Oly@BfM3&jW1>go0oqKc5>Uea z{?nf@RGhFw|FpryCH|#@OTq{#^v&Y(A%O7y2u+cm zn;Ri#&hmorBGp`N-3STv*I0>=LQ-&~4N4R(0)vYqB~W5wVm45euml<|3WFkHXj>66 d8Q}jd`6~rIy$GrCYePg~qLM%`SXo;I_&;({;Q;^u literal 0 HcmV?d00001 diff --git a/iMEGA/Images.xcassets/User alerts/Contents.json b/iMEGA/Images.xcassets/User alerts/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/iMEGA/Images.xcassets/User alerts/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/User alerts/Notifications.imageset/Contents.json b/iMEGA/Images.xcassets/User alerts/Notifications.imageset/Contents.json new file mode 100644 index 0000000000..5ae9129d8c --- /dev/null +++ b/iMEGA/Images.xcassets/User alerts/Notifications.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Notifications.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iMEGA/Images.xcassets/User alerts/Notifications.imageset/Notifications.pdf b/iMEGA/Images.xcassets/User alerts/Notifications.imageset/Notifications.pdf new file mode 100644 index 0000000000000000000000000000000000000000..df52151afe1e3854e9dc3223e9bf0e883c195576 GIT binary patch literal 4733 zcmai22UHW=+NDHFKtQDkqKl+YBVs&q_3=*=W_sZyj#m!g1FK@6avB1lJy zAOYzrASLuFNELa(tN-=>@2>S`&6+v0&o_I%IrGh4`y4@Cq`CxD5)Kw@B7Y^%KVEy( z)zl0|01&_hZ4bV10g%$hI@=Iz0ci@P4@jxoITElq%GnV`z#_327b`5Fs0hXra9ETR z*pu2RN$Z|~EVJ6?EimzLx%j2t@(o7sK6=eT5!zY0k8b{T%WyuJ!+wvVmmMDxS5P8KrW&c) z6JJp&g6_|vUv}Z~=8M^4r~9A1s-itVB72^bh2Yn`jd*&~t!JCew5f!rfAa0S^GngkEql~67(RMc-MkcxOANrC=Z5~wqWQ1u%*G^zs{S28~ zw{eXlc}3nfHZ>^Y@)S|mv76Ws*j}(VGa7+knwc`Yd9jz}pPt$XKDCqu5)3~PNXG=9kR zGsY%PYzLMjq*hQO+(CUNC{g@Dih8N~u?4knLnRHaNa9rS)%Z8&Lj~!Nz4_@hI6Y#w zISZ~ZY6ii9O2`VYjR)tYAgU30==;xfC)cfWj%`MNdB-1b>KflR;1VcdLNAt}cqg=T zl3IA}keC$|XW)YK!)1h^l1xsNfa~aTYb5kbCbCustz@O~*=gH@UfPPI1P;QHLp1nO zPL(>A+6P7qcI{z_zM?R{qNVfXK$8tU-K)pckzyy_A9s65;13zle*{xiXs6fg&5Uks8?!)KwM#mW&c2S?cb*Wo+@y^M5>80TJ9ud|iS z1g|H8JGo_@=hWpn#QXVvn5sQxP4klusNhuXolyQk5XCtux>0+3Xa@^*NZ$3y4-_aA zi@YKlr6wC439@ds_`)l~v14t!6hb@!=V>%ju5w(EDsoJz?~gDM5h0}q73xkhFjR-q zMU5q<%=C7xjqg!8B*qufY#6}3IeP54{L|(3ygO?+%k&gdRyb#;r#($cP||dT^pe5 z??D=Wa%X(TMBkFfgq?OyI@3E?%klA+vT}P}i`yyQv*}N_!eckTsG+0Bvm{Vko`sQ2gkWTIDUzi z+|MYftCWPKln;cN6cX!hhr#M=sQhm|OTX`7XfVVXMyi&r?mEf`2(I&ZGXfvv zH90_f;aUN@G@-#l?#M&x>_6vmmwnEjM*qN&7j3S7>!CIkggw==V_GQ7F&w7G&b<`=m4=ETu}SU?jwNzi&NhR} zGv8)10u-)=&Ob7cVn?27wcc4RGuTE$#S$B@jNSFrCasq<--B=kl(^hiqZTxFO0|t! zB=RmU8uw4xZr2Wtp7Jx;Q)uvTzD!~%}KQ(VDjK(SxwS4s&Xef zCGyx)on?UP>mjQ0k%w!iLLDvQo5q=inIab!9P!kd4w7Osvb&imv*nq z=W|^QKQ1Fo&+V7)z;5eiN#z?Nr(2;#-|C`3rOHn$;C#`W#&_P74GM0p zd1FhAE5XOWynjsZoMtkMa30fE<};YuV2-iE31*9P=ZpqB*3%q~r6)WBBLXEMG~zc8 z^9I0OX-5F6-~dfF+WWMUnlyGwpy$epgH&K;=peQG9eY=*-azjz(A6LrS2{D$jk?3G zOw0iqO5o7?%Mj|3Ks$z@6*i7+2$ok#Rt0D^9h+Bf(}3QZ5{@F3O8fT2=(UIvYZ_Af|Jj)(MI zb)UvARd6b@ylUVYJF!9~9%9*$z@XI1K-;Zu#BgF2sS;}>Kz-zlLmXc&O`%#^EKBZ* z*U_4Bw~)$2pt1C&G-Q#?aSM{pd@Oz7$N#b#1OG8EZCp;C4_Ry6;)Ok?_-t z?u;@=9z;ttUB4M~)3h}C8SNlyL7tbTrH*@?*VV7$xEah@x)}6|bs(xLV7~G2n9!={ zo3kVA%R#L5_!ac+Gc{m;UjI|0F{hhrg<}~_Kn_8yXYK<6jsk{kJPgwed%;H<5PE?H zIt0E@Hm@c#wCJ;YgUWBTc!jPeuxo`1aOn$(rj`d9YT5Id3n>UypGjytZ>51dZmeXX zQ!J1PSPHVnU(`($5n|W5#H*$nA@h=X$Yt;J=^7w6x8VBRc~J>bRZ%f(h^g143DPH-5<83i zH=;?~XPwX9PU1_lPNGhVd7^#IrH{QY{fW~PonDB&kUh$N$bPp^vM8GLnB&<0VaHm0y{?GVoB*xh!QQ zWm%%PoTaFjNzBZx)u=V~e^=K9e7qT{{Gh%dDZ2}gsD(zc)C`+R&?7HgTdYr!<&_)auTz{l&uh=NV=*eyY#cM@l*BwM{{ShxKp(AaAyt^GuH}Nuf%a&_pPxCUR<1{m1LBqr)yU4wNFVCCi6QTR|cY| z-J6`6h6fdf_9}M8STD22X+*XpK2DhCAu(CpO)j|+ccbK0{3d$Ye)+NDdBt$WXvJeS z?lnceQd_7!nmzZO;kAL4>79XH9nd+@Q^px^E=V18onDa%#(4CQ8>q3Ky1pvtwA2Sn z0kd9C8kO&=?jaW#rJ2n+cJRj~#Su@17Q8C1dHQ*JfF#grNY$n)&6w5?ts0ThoR>L0 z)Y~-Ps`Y7HQS(ttR=b>RByL`Gmo<70hBjAoJ-2520|`}OyYbV_V$;#9=3ChB zaiHYj=fT~`p*j`E8yZqF^h22K_x#wO;7!V9|#-`fS3Z-*ZZS11AIrgn)KE?@f z(i~c8S8tETo)~qG80pn}q*q#V)3$7Rs#)c=ir!eYM5if><6B1#$HigmGNS#bWIl`T zZ|L^%h;bdk&++r4#=bYUhdVZc7pwC&@^(GGxHEWs9-ggSGfG7pTMTs-e=#SLJHBRr zeN}a1q2@Zu^wv#QyBMpOa#+Fh>Psc|8hwxFa_0hhV$!Cn=X_o*rhjbtG*CP0JU{E%K;T0GJom|SA9`WjdGse1SOwd5k3VmWhp-*n&V#n8Rz{!>*M z_bnIv>}FpqJ5|rRmhIi2Al|Dv96csO_j>?`P|8SK6bJdnQ6^MSr~9OzJMm^z+!Qwn4WwtvGbgr(V1? zQcxqe)4$;~Z8bC5<6*a)|2Sf#sOI8E;9k})X1yhDc0p%LCn+=fqQ`Bj6eFxmv2KDkwbG3OF#Z`hfZGkOLF_+Y|rovGIV^ z6|9vVO2x$!FojS;U2KrB51crdhAY>7669`0@vi}|VM~;-4fR!7DvfS`9 z56bF;DrJ>{KuG|3VB>#sQot)NR)1dqoxLXxYYm0~P&gR!e;XhpEe)3jtbtz`f|3XD zvlnpwjX@!@luvfB{f)upqDQl#(VLaCX9x}XfCmb0}B zrGWlwBPk_xK?W;>hC$9D;L;EX#!40o$6{olC^-mBT1FayhQSrV|2yQb62ucIrSbEF PAmC6Lu%Mutjym{1(9$zM literal 0 HcmV?d00001 diff --git a/iMEGA/Info.plist b/iMEGA/Info.plist index dfca64558b..f8721919b0 100644 --- a/iMEGA/Info.plist +++ b/iMEGA/Info.plist @@ -36,7 +36,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.2.1 + 4.3 CFBundleSignature ???? CFBundleURLTypes @@ -51,7 +51,7 @@ CFBundleVersion - 123 + 129 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index 36bbe50751..973ecab5c5 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="You can’t download an empty folder"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Your account has already been activated. Please log in."; +"accountAlreadyConfirmed"="Your account has been activated. Please log in."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Invalid link"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -91,18 +91,18 @@ /* Button title which triggers the action to create a MEGA account */ "createAccount"="Create Account"; /* Message shown when the user writes a wrong email or password on login */ -"invalidMailOrPassword"="Invalid e-mail and/or password. Please try again"; +"invalidMailOrPassword"="Invalid email and/or password. Please try again"; /* Error message when to many attempts to login */ "tooManyAttemptsLogin"="You have attempted to log in too many times. Please wait until %@ and try again."; /* Text shown just after creating an account to remenber the user what to do to complete the account creation proccess */ -"accountNotConfirmed"="Please check your e-mail and follow the link to confirm your account."; +"accountNotConfirmed"="Please check your email and follow the link to confirm your account."; /* Error message when trying to login and the account is blocked */ "accountBlocked"="Your account has been blocked. Please contact support@mega.nz"; /* Message shown when the user writes an invalid format in the email field */ "emailInvalidFormat"="Enter a valid email"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="The email address format is invalid"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Enter a valid password"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,22 +130,20 @@ "passwordStrong"="This password will withstand most sophisticated brute-force attacks. Please ensure that you will remember it."; /* */ "agreeWithTheMEGATermsOfService"="I agree with the MEGA Terms of Service"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Enter a valid name"; -/* */ -"invalidFirstNameAndLastName"="Invalid first name and/or last name."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Passwords do not match"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ "termsCheckboxUnselected"="You need to agree with the Terms of Service to register an account on MEGA."; /* Error text shown when the users tries to create an account with an email already in use */ -"emailAlreadyRegistered"="This e-mail address has already registered an account with MEGA"; +"emailAlreadyRegistered"="This email address has already registered an account with MEGA"; /* Title shown just after doing some action that requires confirming the action by an email */ "awaitingEmailConfirmation"="Awaiting email confirmation."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Please enter your password to confirm your account"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirm your account"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirm account"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Wrong password"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -699,7 +697,7 @@ /* Detailed explanation of how the master encryption key (now renamed "Recovery Key") works, and why it is important to remember your password. */ "masterKey_firstParagraph"="Your data is only readable through a chain of decryption operations that begins with your master encryption key (Recovery Key), which we store encrypted with your password. This means that if you lose your password, your Recovery Key can no longer be decrypted, and you can no longer decrypt your data."; /* */ -"masterKey_thirdParagraph"="An external attacker cannot gain access to your account with just your key. A password reset requires both the key and access to your e-mail."; +"masterKey_thirdParagraph"="An external attacker cannot gain access to your account with just your key. A password reset requires both the key and access to your email."; /* Message of the dialog displayed when copy the user's Recovery Key to the clipboard to be saved or exported. */ "recoveryKeyCopiedToClipboard"="The Recovery Key has been copied to the clipboard. Please store it in a safe place."; /* Alert title shown when you have exported your MEGA Recovery Key */ @@ -826,11 +824,11 @@ /* Error message when a use wants to validate the same email twice */ "emailAddressChangeAlreadyRequested"="You have already requested a confirmation link for that email address."; /* Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess */ -"emailIsChanging_description"="Please go to your inbox and click the link to confirm your new e-mail address."; +"emailIsChanging_description"="Please go to your inbox and click the link to confirm your new email address."; /* Text shown on the confirm email view to remind the user what to do */ "verifyYourEmailAddress_description"="Please enter your password to verify your email address"; /* The [X] will be replaced with the e-mail address. */ -"congratulationsNewEmailAddress"="Congratulations, your new e-mail address for this MEGA account is: [X]"; +"congratulationsNewEmailAddress"="Congratulations, your new email address for this MEGA account is: [X]"; /* Button text to close other login sessions except the current session in use. This will log out other devices which have an active login session. */ "closeOtherSessions"="Close other sessions"; /* Message shown when you click on 'Close other session' to block other login sessions except the current session in use. This message is shown when this has been done. */ @@ -846,7 +844,7 @@ /* Menu item */ "help"="Help"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Help centre"; +"helpCentreLabel"="Help Centre"; /* Section title that links you to the webpage that let you join and test the beta versions */ "Join Beta"="Join Beta"; /* Title to rate the app */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="You can upgrade later in My Account"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Cloud Drive is almost full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 4 TB (4096 GB)[/S] of cloud storage space."; +"cloudDriveIsAlmostFull"="[S]Cloud Drive is almost full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 8 TB (8192 GB)[/S] of cloud storage space."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Your account is full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 4 TB (4096 GB)[/S] of cloud storage space."; +"cloudDriveIsFull"="[S]Your account is full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 8 TB (8192 GB)[/S] of cloud storage space."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Storage"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Call failed"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Call ended"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1577,11 +1575,11 @@ /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ -"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticaor, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticaor, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1589,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1627,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* notification subtitle of incoming calls */ @@ -1639,7 +1637,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="File Management"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1660,8 +1658,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="All current files will remain. Only historic versions of your files will be deleted."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Rubbish-Bin Cleaning Scheduler:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Remove files older than"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1671,6 +1669,112 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ "Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="List view"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Thumbnail view"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="List view"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Thumbnail view"; +/* There are no notifications to display. */ +"No notifications"="No notifications"; +/* The header of a notification related to payments */ +"Payment info"="Payment info"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO membership plan expiring soon"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Takedown notice"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Takedown reinstated"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Sent you a contact request"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Cancelled their contact request"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Reminder: You have a contact request"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Deleted you as a contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Contact relationship established"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Account has been deleted/deactivated"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Blocked you as a contact"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="You ignored a contact request"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="You accepted a contact request"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="You denied a contact request"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Accepted your contact request"; +/* When somebody denied your contact request */ +"Denied your contact request"="Denied your contact request"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Access to folders was removed."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Removed [X] items from a share"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Your payment for the %1 plan was received."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Your payment for the %1 plan was unsuccessful."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Your PRO membership plan will expire in 1 day."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Your PRO membership plan will expire in %1 days."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Your publicly shared %1 (%2) has been taken down."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Your taken down %1 (%2) has been reinstated."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Today"; diff --git a/iMEGA/Languages/ar.lproj/Localizable.strings b/iMEGA/Languages/ar.lproj/Localizable.strings index fff9b27b5f..4544077f18 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.strings +++ b/iMEGA/Languages/ar.lproj/Localizable.strings @@ -102,7 +102,7 @@ "emailInvalidFormat"="ادخل يريد إليكتروني صالح"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="صيغة البريد الاليكنروني غير صالحة"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="أدخل كلمة السر الصحيحة"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="البريد الإلكتروني"; @@ -130,10 +130,8 @@ "passwordStrong"="كلمة السر هذه تستطيع تحمل الهجمات القوية وأكثرها تطورا. يرجى التأكد من أنك سوف تتذكرها."; /* */ "agreeWithTheMEGATermsOfService"="أنا أتفق مع MEGA شروط الخدمة"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="ادخل اسم صالح"; -/* */ -"invalidFirstNameAndLastName"="الاسم الأول و / أو اسم العائلة غير صالح ."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="كلمات السر غير متطابقة"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="بانتظار تأكيد بريدك الالكتروني."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="يرجى إدخال كلمة السر لتأكيد حسابك"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="تأكيد حسابك"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="أكّد الحساب"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="كلمة مرور خاطئة"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="إدارة الملف"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="كل الملفات الحالية ستبقى دون تعديل، فقد النسخ القديمة للملفات سوف تحذف"; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="جدولة تنظيف سلة المهملات:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="أزل الملفات الأكبر من"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="يمكنك افراغ سلة المحذوفات بشكل أتوماتيكي. أقصر مدة يمكن تحديدها هي 7 أيام"; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="هل تريد انهاء كل الجلسات الفعالة حاليا؟ ذلك سيسجل الخروج من كل هذه الجلسات الفعالة باستثناء الحالية"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="هل تريد انهاء كل الجلسات الفعالة حاليا؟ ذلك سيسجل الخروج من كل هذه الجلسات الفعالة باستثناء الحالية"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="طريقة عرض القائمة"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="عرض الصور مصغرة"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="طريقة عرض القائمة"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="عرض الصور مصغرة"; +/* There are no notifications to display. */ +"No notifications"="لا يوجد إشعارات جديدة"; +/* The header of a notification related to payments */ +"Payment info"="معلومات الدفع"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="عضوية PRO ستنتهي قريبا"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="إحباط"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="إعادة إنهاء الخدمة"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="طلبات الإضافة المرسلة"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="إلغاء طلب جهات الاتصال الخاصة بهم"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="تذكير: لديك طلب اضافة جهة الاتصال"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="تم حذفك من جهة الاتصال"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="انتم الان متصلين"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="الحساب قد تم حذفه / تعطيلة"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="تم حظرك كجهة اتصال"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="لقد تجاهلت طلب اضافة جهة الاتصال"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="هل قبلت طلب اضافة جهة الاتصال"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="لقد رفضت طلب اضافة جهة الاتصال"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="تمت الموافقة على طلب أصافة جهة الاتصال"; +/* When somebody denied your contact request */ +"Denied your contact request"="تم رفض طلب إضافة جهة الاتصال"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="تمت الإزالة من الدخول إلى المجلدات"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="تم إزالة [X] عناصر من المشاركة"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="تم استلام عملية الدفع الخاصة بك من اجل الخطة %1"; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="عملية الدفع للخطة %1 لم تنجح"; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="عضويتك كـ PRO ستنتهي في يوم واحد"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="عضويتك كـ PRO ستنتهس في %1 أيام."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="لقد شاركت بشكل عام %1 (%2)"; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="تم إعادة الخدمة ل %1 (%2)"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="اليوم"; \ No newline at end of file diff --git a/iMEGA/Languages/de.lproj/Localizable.strings b/iMEGA/Languages/de.lproj/Localizable.strings index b8d0f0152f..e714680131 100644 --- a/iMEGA/Languages/de.lproj/Localizable.strings +++ b/iMEGA/Languages/de.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Ein leerer Ordner kann nicht heruntergeladen werden"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Ihr Account wurde bereits aktiviert. Bitte melden Sie sich an."; +"accountAlreadyConfirmed"="Ihr Account wurde aktiviert. Bitte melden Sie sich an."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Ungültiger Link"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Bitte geben Sie eine gültige E-Mail-Adresse ein"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Das Format der E-Mail-Adresse ist ungültig."; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Geben Sie ein gültiges Passwort ein"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="E-mail"; @@ -130,10 +130,8 @@ "passwordStrong"="Dieses Passwort wird auch gut ausgestatteten Angreifern standhalten. Bitte stellen Sie sicher, dass Sie es nicht vergessen."; /* */ "agreeWithTheMEGATermsOfService"="Ich stimme MEGAs Allgemeinen Geschäftsbedingungen zu"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Gültigen Namen eingeben"; -/* */ -"invalidFirstNameAndLastName"="Vor- und/oder Nachname ungültig."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Passwörter stimmen nicht überein"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Ein Bestätigungslink ist Ihnen per E-Mail zugegangen."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Zur Aktivierung Ihres Accounts geben Sie bitte Ihr Passwort ein"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Bitte bestätigen Sie Ihr Konto"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Account aktivieren"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Falsches Passwort"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -846,7 +844,7 @@ /* Menu item */ "help"="Hilfe"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Onlinehilfe"; +"helpCentreLabel"="Hilfe"; /* Section title that links you to the webpage that let you join and test the beta versions */ "Join Beta"="Join Beta"; /* Title to rate the app */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Sie können dies jederzeit unter Mein Account nachholen"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Ihr Cloud Drive ist fast voll.[/S] [A]Wechseln Sie jetzt[/A] auf einen PRO-Plan und erhalten Sie [S]bis zu 4 TB (4096 GB)[/S] Speicherplatz in der Cloud."; +"cloudDriveIsAlmostFull"="[S]Ihr Cloud Drive ist fast voll.[/S] [A]Wechseln Sie jetzt[/A] auf einen PRO-Plan und erhalten Sie [S]bis zu 8 TB (8192 GB)[/S] Speicherplatz in der Cloud."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Ihr Account hat sein Speicherlimit erreicht.[/S] [A]Wechseln Sie jetzt[/A] auf einen PRO-Account und erhalten Sie [S]bis zu 4 TB (4096 GB)[/S] Speicherplatz in der Cloud."; +"cloudDriveIsFull"="[S]Ihr Account hat sein Speicherlimit erreicht.[/S] [A]Wechseln Sie jetzt[/A] auf einen PRO-Account und erhalten Sie [S]bis zu 8 TB (8192 GB)[/S] Speicherplatz in der Cloud."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Speicher"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1576,6 +1574,8 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Zwei-Faktor-Authentfizierung ist eine zusätzliche Sicherheitsvorkehrung, die den Zugang zu Ihrem Account schützt. Selbst bei Kenntnis Ihres Passworts können unberechtigte Dritte nur dann auf Ihren Account zugreifen, wenn sie den von der Authentifizierungs-App angezeigten sechsstelligen Code kennen."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Sie benötigen eine Zwei-Faktor-Authentifizierungs-App auf diesem Gerät"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Zwei-Faktor-Authentifizierung aktiviert"; /* A message on the dialog shown after 2FA was successfully enabled. */ @@ -1590,17 +1590,17 @@ "scanOrCopyTheSeed"="Scannen Sie diesen Code mit Ihrer Authentifizierungs-App (oder kopieren Sie Ihn). Für den Fall des Verlusts Ihres Mobilgeräts bewahren Sie bitte eine Kopie an einem sicheren Ort auf."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Authentifizierungsgerät verloren?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,11 +1635,11 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Dateiverwaltung"; /* Title of the option to enable or disable file versioning on Settings section */ -"File versioning"="File versioning"; +"File versioning"="Dateiversionierung"; /* Subtitle of the option to enable or disable file versioning on Settings section */ "Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Enable or disable file versioning for your entire account.\nYou may still receive file versions from shared folders if your contacts have this enabled."; /* A confirmation message when the user chooses to disable file versioning. */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Alle alten Dateiversionen werden gelöscht. Dateiversionen in Ordnerfreigaben eines Kontakts müssen vom Eigentümer entfernt werden werden."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Papierkorb automatisch leeren:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Automatische Löschung nach"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Der Papierkorb wird automatisch für Sie geleert. Die Mindestaufbewahrungsfrist für gelöschte Dateien und Ordner beträgt sieben Tage."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Alle anderen Sitzungen schließen? Sie werden überall außer auf diesem Gerät ausgeloggt."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Alle anderen Sitzungen schließen? Sie werden überall außer auf diesem Gerät ausgeloggt."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Listenansicht"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Thumbnail-Ansicht"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="Listenansicht"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Thumbnail-Ansicht"; +/* There are no notifications to display. */ +"No notifications"="Keine Ereignisse"; +/* The header of a notification related to payments */ +"Payment info"="Informationen zur Zahlung"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Ihre PRO-Mitgliedschaft läuft bald ab"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Löschung durch Dritte"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Takedown rückgängig gemacht"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="hat Ihnen eine Kontaktanfrage geschickt"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="hat das Kontaktersuchen zurückgezogen"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Erinnerung: Sie haben ein wartendes Kontaktersuchen"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="hat Sie gelöscht"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Kontakt hergestellt"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Account wurde gelöscht/deaktiviert"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="hat Sie blockiert"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Kontaktersuchen ignoriert"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Kontaktersuchen angenommen"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Sie haben das Kontaktersuchen abgelehnt"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Kontaktanfrage wurde akzeptiert"; +/* When somebody denied your contact request */ +"Denied your contact request"="Ihre Kontaktanfrage wurde abgelehnt"; +/* notification text */ +"A user has left the shared folder {0}"="Ein Benutzer hat die Ordnerfreigabe {0} verlassen"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Ordnerfreigabe storniert."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="[X] Dateien und Ordner aus Freigabe entfernt"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Wir haben Ihre Zahlung für %1 erhalten."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Ihre Zahlung für %1 ist fehlgeschlagen."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Ihr PRO-Status läuft in weniger als 24 Stunden ab."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Ihr PRO-Status läuft in %1 Tagen ab."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Ihr per exportiertem Link zugänglich gemachter %1 (%2) wurde auf eine Beschwerde hin gelöscht."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Die Löschung von %1 (%2) wurde rückgängig gemacht."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Zuletzt aktiv %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Heute"; \ No newline at end of file diff --git a/iMEGA/Languages/en.lproj/Localizable.strings b/iMEGA/Languages/en.lproj/Localizable.strings index 80933d45f5..517ece44df 100644 --- a/iMEGA/Languages/en.lproj/Localizable.strings +++ b/iMEGA/Languages/en.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="You can’t download an empty folder"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Your account has already been activated. Please log in."; +"accountAlreadyConfirmed"="Your account has been activated. Please log in."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Invalid link"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -91,18 +91,18 @@ /* Button title which triggers the action to create a MEGA account */ "createAccount"="Create Account"; /* Message shown when the user writes a wrong email or password on login */ -"invalidMailOrPassword"="Invalid e-mail and/or password. Please try again"; +"invalidMailOrPassword"="Invalid email and/or password. Please try again"; /* Error message when to many attempts to login */ "tooManyAttemptsLogin"="You have attempted to log in too many times. Please wait until %@ and try again."; /* Text shown just after creating an account to remenber the user what to do to complete the account creation proccess */ -"accountNotConfirmed"="Please check your e-mail and follow the link to confirm your account."; +"accountNotConfirmed"="Please check your email and follow the link to confirm your account."; /* Error message when trying to login and the account is blocked */ "accountBlocked"="Your account has been blocked. Please contact support@mega.nz"; /* Message shown when the user writes an invalid format in the email field */ "emailInvalidFormat"="Enter a valid email"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="The email address format is invalid"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Enter a valid password"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,22 +130,20 @@ "passwordStrong"="This password will withstand most sophisticated brute-force attacks. Please ensure that you will remember it."; /* */ "agreeWithTheMEGATermsOfService"="I agree with the MEGA Terms of Service"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Enter a valid name"; -/* */ -"invalidFirstNameAndLastName"="Invalid first name and/or last name."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Passwords do not match"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ "termsCheckboxUnselected"="You need to agree with the Terms of Service to register an account on MEGA."; /* Error text shown when the users tries to create an account with an email already in use */ -"emailAlreadyRegistered"="This e-mail address has already registered an account with MEGA"; +"emailAlreadyRegistered"="This email address has already registered an account with MEGA"; /* Title shown just after doing some action that requires confirming the action by an email */ "awaitingEmailConfirmation"="Awaiting email confirmation."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Please enter your password to confirm your account"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirm your account"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirm account"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Wrong password"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -699,7 +697,7 @@ /* Detailed explanation of how the master encryption key (now renamed "Recovery Key") works, and why it is important to remember your password. */ "masterKey_firstParagraph"="Your data is only readable through a chain of decryption operations that begins with your master encryption key (Recovery Key), which we store encrypted with your password. This means that if you lose your password, your Recovery Key can no longer be decrypted, and you can no longer decrypt your data."; /* */ -"masterKey_thirdParagraph"="An external attacker cannot gain access to your account with just your key. A password reset requires both the key and access to your e-mail."; +"masterKey_thirdParagraph"="An external attacker cannot gain access to your account with just your key. A password reset requires both the key and access to your email."; /* Message of the dialog displayed when copy the user's Recovery Key to the clipboard to be saved or exported. */ "recoveryKeyCopiedToClipboard"="The Recovery Key has been copied to the clipboard. Please store it in a safe place."; /* Alert title shown when you have exported your MEGA Recovery Key */ @@ -826,11 +824,11 @@ /* Error message when a use wants to validate the same email twice */ "emailAddressChangeAlreadyRequested"="You have already requested a confirmation link for that email address."; /* Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess */ -"emailIsChanging_description"="Please go to your inbox and click the link to confirm your new e-mail address."; +"emailIsChanging_description"="Please go to your inbox and click the link to confirm your new email address."; /* Text shown on the confirm email view to remind the user what to do */ "verifyYourEmailAddress_description"="Please enter your password to verify your email address"; /* The [X] will be replaced with the e-mail address. */ -"congratulationsNewEmailAddress"="Congratulations, your new e-mail address for this MEGA account is: [X]"; +"congratulationsNewEmailAddress"="Congratulations, your new email address for this MEGA account is: [X]"; /* Button text to close other login sessions except the current session in use. This will log out other devices which have an active login session. */ "closeOtherSessions"="Close other sessions"; /* Message shown when you click on 'Close other session' to block other login sessions except the current session in use. This message is shown when this has been done. */ @@ -846,7 +844,7 @@ /* Menu item */ "help"="Help"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Help centre"; +"helpCentreLabel"="Help Centre"; /* Section title that links you to the webpage that let you join and test the beta versions */ "Join Beta"="Join Beta"; /* Title to rate the app */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="You can upgrade later in My Account"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Cloud Drive is almost full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 4 TB (4096 GB)[/S] of cloud storage space."; +"cloudDriveIsAlmostFull"="[S]Cloud Drive is almost full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 8 TB (8192 GB)[/S] of cloud storage space."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Your account is full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 4 TB (4096 GB)[/S] of cloud storage space."; +"cloudDriveIsFull"="[S]Your account is full.[/S] [A]Upgrade now[/A] to a PRO account and get [S]up to 8 TB (8192 GB)[/S] of cloud storage space."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Storage"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Call failed"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Call ended"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="File Management"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="All current files will remain. Only historic versions of your files will be deleted."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Rubbish-Bin Cleaning Scheduler:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Remove files older than"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ "Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="List view"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Thumbnail view"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="List view"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Thumbnail view"; +/* There are no notifications to display. */ +"No notifications"="No notifications"; +/* The header of a notification related to payments */ +"Payment info"="Payment info"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO membership plan expiring soon"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Takedown notice"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Takedown reinstated"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Sent you a contact request"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Cancelled their contact request"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Reminder: You have a contact request"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Deleted you as a contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Contact relationship established"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Account has been deleted/deactivated"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Blocked you as a contact"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="You ignored a contact request"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="You accepted a contact request"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="You denied a contact request"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Accepted your contact request"; +/* When somebody denied your contact request */ +"Denied your contact request"="Denied your contact request"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Access to folders was removed."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Removed [X] items from a share"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Your payment for the %1 plan was received."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Your payment for the %1 plan was unsuccessful."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Your PRO membership plan will expire in 1 day."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Your PRO membership plan will expire in %1 days."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Your publicly shared %1 (%2) has been taken down."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Your taken down %1 (%2) has been reinstated."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Today"; diff --git a/iMEGA/Languages/es.lproj/InfoPlist.strings b/iMEGA/Languages/es.lproj/InfoPlist.strings index 3216aa4970..1e8d787ce6 100644 --- a/iMEGA/Languages/es.lproj/InfoPlist.strings +++ b/iMEGA/Languages/es.lproj/InfoPlist.strings @@ -7,7 +7,7 @@ /* Contacts Usage Description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ "NSContactsUsageDescription"="MEGA accede a tus contactos cuando eliges alguno para guardarlo/exportarlo."; /* Write-only access to the user’s photo library description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ -"NSPhotoLibraryAddUsageDescription"="MEGA necesita acceso a Fotos para agregar fotos y vídeos a la galería de tu dispositivo"; +"NSPhotoLibraryAddUsageDescription"="MEGA necesita acceso a Fotos para añadir fotos y vídeos a la galería de tu dispositivo"; /* Face ID usage description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ "NSFaceIDUsageDescription"="MEGA accede a Face ID para que puedas desbloquear fácilmente el código de acceso de la aplicación cuando activas esta opción."; /* Label for any 'Search' button, link, etc. - (String as short as possible). */ diff --git a/iMEGA/Languages/es.lproj/Localizable.strings b/iMEGA/Languages/es.lproj/Localizable.strings index 466330db32..164d99a696 100644 --- a/iMEGA/Languages/es.lproj/Localizable.strings +++ b/iMEGA/Languages/es.lproj/Localizable.strings @@ -27,17 +27,17 @@ /* Alert title to attract attention */ "attention"="Atención"; /* Alert message to remember that MEGA app needs permission to use the Camera to take a photo or video and it doesn't have it */ -"cameraPermissions"="Por favor dale permisos a la aplicación de MEGA para acceder a tu cámara en Ajustes"; +"cameraPermissions"="Por favor permite a la aplicación de MEGA que acceda a tu cámara en Ajustes"; /* Alert message to notify that MEGA video calls needs the Camera permission and the call will end*/ -"cameraPermissionsInCall"="Por favor dale permisos a la aplicación de MEGA para acceder a tu cámara en Ajustes. Al hacer esto, la llamada finalizará porque la aplicación se reiniciará."; +"cameraPermissionsInCall"="Por favor permite que la aplicación de MEGA acceda a tu cámara en Ajustes. Al hacer esto, la llamada finalizará porque la aplicación se reiniciará."; /* Alert message to remember that MEGA app needs permission to use the Microphone to make calls and record videos and it doesn't have it */ -"microphonePermissions"="Por favor dale permisos a la aplicación de MEGA para acceder a tu micrófono en Ajustes"; +"microphonePermissions"="Por favor permite que la aplicación de MEGA acceda a tu micrófono en Ajustes"; /* Alert message to explain that the MEGA app needs permission to access your device photos */ -"photoLibraryPermissions"="Por favor dale permisos a la aplicación de MEGA para acceder a tus Fotos en Ajustes"; +"photoLibraryPermissions"="Por favor permite a la aplicación de MEGA que acceda a tus Fotos en Ajustes"; /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="No se pueden descargar carpetas vacías"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Tu cuenta ya ha sido activada. Por favor, inicia sesión."; +"accountAlreadyConfirmed"="Tu cuenta está activa. Por favor, inicia sesión."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Enlace no válido"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -53,7 +53,7 @@ /* Alert title shown when your account has been closed through another client or the web by killing all your sessions */ "loggedOut_alertTitle"="Sesión cerrada"; /* Alert message shown when your account has been closed through another client or the web by killing all your sessions */ -"loggedOutFromAnotherLocation"="Tu sesión ha sido cerrada de este dispositivo desde otra localización"; +"loggedOutFromAnotherLocation"="La sesión en este dispositivo ha sido cerrada desde otra localización"; /* Alert title shown when the app detects that the Secure Sockets Layer (SSL) key of MEGA can't be verified. */ "sslUnverified_alertTitle"="MEGA no se puede conectar de forma segura a través de SSL. Es posible que estés conectado a una WiFi pública con requisitos adicionales."; /* Message shown when you're trying to open a file or folder from the mobile webclient and the accounts logged are not the same */ @@ -101,8 +101,8 @@ /* Message shown when the user writes an invalid format in the email field */ "emailInvalidFormat"="Introduce un correo electrónico válido"; /* Add contacts and share dialog error message when user try to add wrong email address */ -"theEmailAddressFormatIsInvalid"="El formato del email es incorrecto"; -/* Message shown when the user enters a wrong password */ +"theEmailAddressFormatIsInvalid"="El formato del correo electrónico es incorrecto"; +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Introduce una contraseña válida"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Correo electrónico"; @@ -130,10 +130,8 @@ "passwordStrong"="Esta contraseña podrá soportar los ataques más sofisticados de fuerza bruta. Por favor, asegúrate de recordarla."; /* */ "agreeWithTheMEGATermsOfService"="Acepto los Términos de Servicio de MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Introduce un nombre"; -/* */ -"invalidFirstNameAndLastName"="Nombre y/o apellido(s) no válidos."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Las contraseñas no coinciden"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,12 +142,12 @@ "awaitingEmailConfirmation"="Esperando confirmación por correo electrónico."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Por favor, introduce tu contraseña para confirmar tu cuenta."; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirma tu cuenta"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirmar cuenta"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Contraseña incorrecta"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ -"alreadyLoggedInAlertTitle"="Hay iniciada una sesión con otra cuenta"; +"alreadyLoggedInAlertTitle"="Tienes una sesión abierta con otra cuenta"; /* Warning message shown when you try to confirm an account but you are logged in with another one */ "alreadyLoggedInAlertMessage"="Si aceptas, se cerrará la sesión actual y los datos en Sin conexión serán eliminados. ¿Quieres continuar?"; /* Error shown when the user tries to change his mail to one that is already used */ @@ -171,7 +169,7 @@ /* ToS refers to Terms of Service and AUP refers to Acceptable Use Policy. */ "fileLinkUnavailableText2"="Este archivo ha sido eliminado por infringir los Términos de servicio y/o condiciones de uso."; /* */ -"fileLinkUnavailableText3"="URL Inválida - El enlace al que deseas acceder no existe."; +"fileLinkUnavailableText3"="Enlace inválido - el enlace al que está intentando acceder no existe"; /* */ "fileLinkUnavailableText4"="El archivo ha sido borrado por el usuario."; /* Message shown when a download starts */ @@ -189,7 +187,7 @@ /* */ "folderLinkUnavailableText2"="El enlace de la carpeta ha sido eliminado por infringir nuestros Términos de servicio."; /* */ -"folderLinkUnavailableText3"="URL Inválida - El enlace al que deseas acceder no existe."; +"folderLinkUnavailableText3"="Enlace inválido - el enlace al que está intentando acceder no existe"; /* */ "folderLinkUnavailableText4"="El enlace de la carpeta ha sido desactivado por el usuario."; /* Text shown on the app when you don't have connection to the internet or when you have lost it */ @@ -281,7 +279,7 @@ /* Sort by option (4/6). This one order the files by its size, in this case from smaller to bigger size */ "smallest"="Más pequeño"; /* Sort by option (5/6). This one order the files by its modification date, newer first */ -"newest"="Más nuevo"; +"newest"="Más reciente"; /* Sort by option (6/6). This one order the files by its modification date, older first */ "oldest"="Más antiguo"; /* Menu option from the `Add` section that allows the user to choose a photo or video to upload it to MEGA. */ @@ -305,7 +303,7 @@ /* Message shown when you try to upload one photo or video that is already uploaded in the current folder. “Already uploaded with name {File name} */ "fileAlreadyExistMessage"="Este archivo ya se había subido con el nombre %@"; /* Message shown when you try to upload some photos or/and videos that are already uploaded in the current folder */ -"filesAlreadyExistMessage"="%d archivos de los seleccionados ya han sido subidos a esta carpeta."; +"filesAlreadyExistMessage"="%d archivos de los seleccionados ya se habían subido a esta carpeta."; /* Message shown when a upload starts */ "uploadStarted_Message"="Subida iniciada"; /* Alert title shown when you are seeing the details of a file and you moved it to the Rubbish Bin from another location */ @@ -325,7 +323,7 @@ /* List option shown on the details of a file or folder */ "savedForOffline"="Sin conexión"; /* Error message shown when a user tries to download a folder with the name Inbox on the main directory of the offline section */ -"folderInboxError"="Inbox es un nombre reservado para uso por Apple."; +"folderInboxError"="Inbox es un nombre reservado para uso de Apple."; /* Title shown under the action that allows you to save an image to your camera roll */ "saveImage"="Guardar imagen"; /* Title shown under the action that allows you to save several images to your camera roll */ @@ -393,7 +391,7 @@ /* Toast text upon sending multiple files to chat */ "filesSentToChat"="Archivos enviados al chat"; /* success message when sending multiple files. Please do not modify the %d placeholder. */ -"xfilesSentSuccesfully"="%d archivos enviados correctamente"; +"xfilesSentSuccesfully"="Se han enviado %d archivos"; /* Message shown when there is an active link that can be removed or disabled */ "removeLink"="Eliminar enlace"; /* Message shown when there are some active links that can be removed or disabled */ @@ -511,7 +509,7 @@ /* Text shown to explain what means 'Enable Camera Uploads'. The 'Cloud Drive' is the MEGA section, so please keep consistency */ "automaticallyBackupYourPhotos"="Guarda tus fotos automáticamente en tu Nube"; /* Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA */ -"enableCameraUploadsButton"="Activar Subidas de la cámara"; +"enableCameraUploadsButton"="Activar las Subidas de la cámara"; /* Success message shown when Camera Uploads has been enabled */ "cameraUploadsEnabled"="Subidas de la cámara activadas"; /* Message shown when the camera uploads have been completed */ @@ -591,7 +589,7 @@ /* Add contacts and share dialog error message when user try to add your own email address */ "noNeedToAddYourOwnEmailAddress"="No hace falta que añadas tu propio correo electrónico"; /* Add contacts and share dialog error message when user try to add already existing email address. */ -"alreadyHaveAContactWithThatEmailAddress"="Ya tienes un contacto con ese email"; +"alreadyHaveAContactWithThatEmailAddress"="Ya tienes un contacto con ese correo electrónico"; /* Success message shown when a contact has been invited */ "theUserHasBeenInvited"="El usuario [X] ha sido invitado y aparecerá en tu lista de contactos en cuanto acepte la invitación."; /* Success message shown when some contacts have been invited */ @@ -705,7 +703,7 @@ /* Alert title shown when you have exported your MEGA Recovery Key */ "masterKeyExported"="Clave de recuperación guardada"; /* Alert message shown to explain that the Recovery Key was saved on your device. Also that you can access it through iTunes. And ends with and security advise. */ -"masterKeyExported_alertMessage"="Se ha guardado la Clave de recuperación en la sección Sin conexión como RecoveryKey.txt. Aviso: Se eliminará si te desconectas, por favor almacénala en un lugar seguro."; +"masterKeyExported_alertMessage"="Se ha guardado la Clave de recuperación en la sección Sin conexión como RecoveryKey.txt. Aviso: si te desconectas se eliminará, por favor guárdala en un lugar seguro."; /* Title header that refers to where do you do the actions 'Clear Offlines files' and 'Clear cache' inside 'Settings' -> 'Advanced' section" */ "onYourDevice"="En el dispositivo"; /* Section title where you can 'Clear Offline files' of your MEGA app */ @@ -729,7 +727,7 @@ /* In 'My account', when user want to delete/remove/cancel account will click button named 'Cancel your account' */ "cancelYourAccount"="Cancelar cuenta"; /* Message that is shown when the user click on 'Cancel your account' to confirm that he's aware that his data will be deleted. */ -"youWillLooseAllData"="Perderás todos los datos asociados con esta cuenta. ¿Estas seguro de que quieres continuar?"; +"youWillLooseAllData"="Perderás todos los datos asociados con esta cuenta. ¿Estás seguro de que quieres continuar?"; /* Account closure, warning message to remind user to contact MEGA support after he confirms that he wants to cancel account. */ "ifYouCantAccessYourEmailAccount"="Si no puedes acceder a tu correo electrónico contacta con support@mega.nz."; /* During account cancellation (deletion) */ @@ -791,7 +789,7 @@ /* Headline of the password reset recovery procedure */ "passwordReset"="Restablecer la contraseña"; /* A message shown to explain that the user has to input (type or paste) their recovery key to continue with the reset password process. */ -"pleaseEnterYourRecoveryKey"="Introduce tu Clave de recuperación."; +"pleaseEnterYourRecoveryKey"="Introduce tu Clave de recuperación"; /* Label for any 'Recovery Key' button, link, text, title, etc. Preserve uppercase - (String as short as possible). The Recovery Key is the new name for the account 'Master Key', and can unlock (recover) the account if the user forgets their password. */ "recoveryKey"="Clave de recuperación"; /* An alert title where the user provided the incorrect Recovery Key. */ @@ -828,7 +826,7 @@ /* Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess */ "emailIsChanging_description"="Por favor accede a tu bandeja de entrada y pulsa el enlace para confirmar tu nuevo correo electrónico."; /* Text shown on the confirm email view to remind the user what to do */ -"verifyYourEmailAddress_description"="Introduce tu contraseña para verificar tu dirección de correo electrónico"; +"verifyYourEmailAddress_description"="Introduce la contraseña para verificar tu dirección de correo electrónico"; /* The [X] will be replaced with the e-mail address. */ "congratulationsNewEmailAddress"="Enhorabuena, tu nuevo correo electrónico para esta cuenta MEGA es: [X]"; /* Button text to close other login sessions except the current session in use. This will log out other devices which have an active login session. */ @@ -848,9 +846,9 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="Centro de ayuda"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Únete a Beta"; /* Title to rate the app */ -"rateUsLabel"="Vótanos"; +"rateUsLabel"="Puntúanos"; /* Title of the Transfers section */ "transfers"="Transferencias"; /* Title of one of the filters in the Transfers section. In this case "All" transfers. */ @@ -878,7 +876,7 @@ /* Button title that shows the different MEGA PRO plans to which you can upgrade. (String as short as possible) */ "buyPRO"="Hazte PRO"; /* Caption of a button to upgrade the account to Pro status */ -"upgrade"="Hazte Pro"; +"upgrade"="Hazte PRO"; /* Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile */ "viewAndEditProfile"="Ver y editar tu perfil"; /* Label for any 'Profile' button, link, text, title, etc. - (String as short as possible). */ @@ -894,17 +892,17 @@ /* title of the introduction for the achievements screen */ "inviteFriendsAndGetForEachReferral"="Obtén %1$s de almacenamiento y %2$s de transferencia por cada invitación"; /* Text shown to explain how and where you can invite friends */ -"inviteYourFriendsExplanation"="Selecciona contactos de tu agenda o introduce direcciones de correo electrónico."; +"inviteYourFriendsExplanation"="Selecciona contactos de tu agenda o introduce varias direcciones de correo electrónico."; /* */ "insertYourFriendsEmails"="Introduce el correo electrónico de tu(s) amigo(s):"; /* */ "howItWorks"="Cómo funciona"; /* */ -"howItWorksMain"="Tu amigo tiene que registrarse para obtener una cuenta gratuita en MEGA e [S]instalar al menos una aplicación cliente MEGA[/S] (ya sea el cliente de sincronización o una de nuestras aplicaciones móviles)"; +"howItWorksMain"="Tu amigo tiene que registrarse para obtener una cuenta gratuita en MEGA e [S]instalar al menos una aplicación de MEGA[/S] (ya sea la app de sincronización o una de nuestras aplicaciones móviles)"; /* */ -"howItWorksSecondary"="Puedes notificar a tus amigos por cualquier medio. Obtendrás la cuota si has introducido el correo electrónico de tu amigo aquí antes de que se registre con esa dirección."; +"howItWorksSecondary"="Puedes invitar amigos de varias formas. Obtendrás la cuota si has introducido el correo electrónico de tu amigo aquí antes de que se registre con esa dirección."; /* A message which is shown once someone has invited a friend as part of the achievements program. */ -"howItWorksTertiary"="No recibirás el bono si invitas a alguien que ya haya utilizado MEGA anteriormente y no se te notificará dicho rechazo."; +"howItWorksTertiary"="Si invitas a alguien que ya haya utilizado MEGA anteriormente no recibirás el bono y no se te notificará."; /* Header of block with achievements bonuses. */ "unlockedBonuses"="Bonos desbloqueados:"; /* A header/title of a section which contains information about used/available storage space on a user's cloud drive. */ @@ -952,7 +950,7 @@ /* Hint text for the first name (Placeholder) */ "firstName"="Nombre"; /* Hint text for the last name (Placeholder) */ -"lastName"="Apellidos"; +"lastName"="Apellido(s)"; /* Button title that goes to the section Usage where you can see how your MEGA space is used */ "usage"="Uso"; /* Title for "Local" used space. */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Puedes hacerte PRO más tarde en Mi cuenta"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Tu nube está casi llena.[/S] [A]Hazte PRO ahora[/A] y obtén [S]hasta 4TB (4096 GB)[/S] de espacio de almacenamiento en la nube."; +"cloudDriveIsAlmostFull"="[S]Tu nube está casi llena.[/S] [A]Hazte PRO ahora[/A] y obtén [S]hasta 8TB (8192 GB)[/S] de espacio de almacenamiento en la nube."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Tu cuenta está llena.[/S] [A]Hazte PRO ahora[/A] y obtén [S]hasta 4 TB (4096 GB)[/S] de espacio de almacenamiento en la nube."; +"cloudDriveIsFull"="[S]Tu cuenta está llena.[/S] [A]Hazte PRO ahora[/A] y obtén [S]hasta 8 TB (8192 GB)[/S] de espacio de almacenamiento en la nube."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Almacenamiento"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -994,7 +992,7 @@ /* Label for any 'Limited' button, link, text, title, etc. - (String as short as possible). */ "limited"="Limitado"; /* A message shown at registration time when users have to select their plan. The FREE 50 GB plan is subject to the achievements program. */ -"subjectToYourParticipationInOurAchievementsProgram"="Sujeto a tu participación en nuestro programa de logros."; +"subjectToYourParticipationInOurAchievementsProgram"="Sujeto a la participación en nuestro programa de logros."; /* Price asociated with the MEGA PRO account level you can subscribe */ "productPricePerMonth"="%@ al mes"; /* Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue */ @@ -1008,7 +1006,7 @@ /* Button on the Pro page to request a custom Pro plan because their storage usage is more than the regular plans. */ "requestAPlan"="Solicitar un plan"; /* Asks the user to request a custom Pro plan from customer support because their storage usage is more than the regular plans. */ -"thereAreNoPlansSuitableForYourCurrentUsage"="No hay planes adecuados para tu uso actual. Por favor, [A]contacta con soporte[/A] para un plan a medida."; +"thereAreNoPlansSuitableForYourCurrentUsage"="No hay planes adecuados para tu uso actual. Por favor, [A]contacta con soporte[/A] para un plan personalizado."; /* Header shown to help on the purchasin process */ "selectMembership"="Selecciona una suscripción:"; /* Footer shown to remenber that if you select a yearly plan yo will save up to 17% */ @@ -1048,11 +1046,11 @@ /* Alert message shown when the restoring process has stopped for some reason */ "failedRestore_message"="La petición ha sido cancelada o la compra anterior no se ha podido restaurar. Por favor, inténtalo de nuevo más tarde o contacta con ios@mega.nz."; /* Alert title shown when the DEBUG mode is enabled */ -"enableDebugMode_title"="Activar el modo depuración"; +"enableDebugMode_title"="Activar modo depuración"; /* Alert message shown when the DEBUG mode is enabled */ "enableDebugMode_message"="Se creará un log en la sección Sin conexión (MEGAiOS.log). Los logs pueden contener información relativa a tu cuenta."; /* Alert title shown when the DEBUG mode is disabled */ -"disableDebugMode_title"="Desactivar el modo depuración"; +"disableDebugMode_title"="Desactivar modo depuración"; /* Alert message shown when the DEBUG mode is disabled */ "disableDebugMode_message"="Se eliminará el log (MEGAiOS.log) de la sección Sin conexión."; /* Alert message shown when the user remove one item from the Offline section */ @@ -1098,9 +1096,9 @@ /* Title that refers to the status of the chat (Either Online or Offline) */ "status"="Estado"; /* Title used in settings that enables the generation of link previews in the chat */ -"richUrlPreviews"="Vista previa de la URL enriquecida"; +"richUrlPreviews"="Vista previa del enlace"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ -"richPreviewsFooter"="Mejora tu experiencia con MEGAchat. El contenido de la URL se recuperará sin cifrado punto a punto."; +"richPreviewsFooter"="Mejora tu experiencia con MEGAchat. El contenido del enlace se recuperará sin cifrado punto a punto."; /* Section title of a button where you can enable mobile data for voice and video calls. */ "voiceAndVideoCalls"="Llamadas y videollamadas"; /* Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature. */ @@ -1118,7 +1116,7 @@ /* Label title to enable/disable the feature of the chat status that mantains your chosen status even if you don't have connected devices */ "statusPersistence"="Bloquear estado"; /* Footer text to explain the meaning of the functionaly 'Status Persistence' of your chat status. */ -"maintainMyChosenStatusAppearance"="Mantener el estado elegido aunque no tenga ningún dispositivo conectado."; +"maintainMyChosenStatusAppearance"="Mantener el estado elegido aunque no haya ningún dispositivo conectado."; /* A log message in a chat to indicate that the message has been edited by the user. */ "edited"="(editado)"; /* */ @@ -1198,7 +1196,7 @@ /* Title show above the name of the persons with whom you're chatting */ "chattingWith"="Chateando con"; /* Full text: MEGA protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: Confidentiality - Only the author and intended recipients are able to decipher and read the content. Authenticity - There is an assurance that the message received was authored by the stated sender, and its content has not been tampered with during transport or on the server. */ -"chatIntroductionMessage"="MEGA protege tus comunicaciones con cifrado punto a punto (controlado por el usuario), proporcionando garantías esenciales de seguridad:"; +"chatIntroductionMessage"="MEGA protege tus comunicaciones con cifrado punto a punto (controlado por el usuario), proporcionando sólidas garantías de seguridad:"; /* Chat advantages information. Full text: Mega protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: [S]Confidentiality.[/S] Only the author and intended recipients are able to decipher and read the content. [S]Authenticity.[/S] The system ensures that the data received is from the sender displayed, and its content has not been manipulated during transit. */ "confidentialityExplanation"="[S]Confidencialidad.[/S] Solo el autor y los destinatarios receptores pueden descifrar y leer el contenido."; /* Chat advantages information. Full text: Mega protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: [S]Confidentiality.[/S] Only the author and intended recipients are able to decipher and read the content. [S]Authenticity.[/S] The system ensures that the data received is from the sender displayed, and its content has not been manipulated during transit. */ @@ -1232,7 +1230,7 @@ /* Button label for sending file attachments through the chat to another user. */ "sendFiles"="Enviar archivos"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ -"enableRichUrlPreviews"="Activa la vista previa de la URL enriquecida"; +"enableRichUrlPreviews"="Activar la vista previa del enlace"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ "alwaysAllow"="Permitir siempre"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ @@ -1240,7 +1238,7 @@ /* */ "never"="Nunca"; /* After several times (right now set to 3) that the user may had decided to click \"Not now\" (for when being asked if he/she wants a URL preview to be generated for a link, posted in a chat room), we change the \"Not now\" button to \"Never\". If the user clicks it, we ask for one final time - to ensure he wants to not be asked for this anymore and tell him that he can do that in Settings. */ -"richPreviewsConfirmation"="La vista previa de los URL enrequecidas se desactivará de forma permanente. Puede volver a activarla desde Ajustes. ¿Quieres continuar?"; +"richPreviewsConfirmation"="La vista previa de los enlaces se desactivará de forma permanente. Puedes volver a activarla desde Ajustes. ¿Quieres continuar?"; /* Once a preview is generated for a message which contains URLs, the user can remove it. Same button is also shown during loading of the preview - and would cancel the loading (text of the button is the same in both cases). */ "removePreview"="Eliminar vista previa"; /* */ @@ -1286,7 +1284,7 @@ /* Label in chat rooms that indicates how many messages are unread. Plural and as short as possible. */ "unreadMessages"="%lu mensajes no leídos"; /* Describe how works auto-renewable subscriptions on the Apple Store */ -"autorenewableDescription"="La suscripción se renueva automáticamente con la misma duración y precio elegidos inicialmente. Puedes desactivar la renovación automática de la suscripción A MEGA PRO en la página de ajustes de iTunes hasta 24 horas antes de la renovación programada. Para gestionar tu suscripción, simplemente ve a la App Store, inicia sesión con tu Apple ID al final de la página (si no has hecho esto todavía) y pulsa en “Ver ID de Apple”. Podrás ver la página con los detalles de tu cuenta, donde puedes gestionar las suscripciones que has contratado. Desde aquí, puedes seleccionar la suscripción A MEGA PRO y ver cuando será renovada, cambiar el tipo de suscripción o desactivar la opción de renovación automática para luego cancelar tu suscripción."; +"autorenewableDescription"="La suscripción se renueva automáticamente con la misma duración y precio elegidos inicialmente. Puedes desactivar la renovación automática de la suscripción a MEGA PRO en la página de ajustes de iTunes hasta 24 horas antes de la renovación programada. Para gestionar tu suscripción, simplemente ve a la App Store, inicia sesión con tu Apple ID al final de la página (si aún no lo has hecho) y pulsa en “Ver ID de Apple”. Podrás ver la página con los detalles de tu cuenta, donde puedes gestionar las suscripciones que has contratado. Desde aquí, puedes seleccionar la suscripción a MEGA PRO y ver cuando se renovará, cambiar el tipo de suscripción o desactivar la opción de renovación automática para luego cancelar tu suscripción."; /* Text shown when you try to use a MEGA extension in iOS and you aren't logged */ "openMEGAAndSignInToContinue"="Abre MEGA e inicia sesión para continuar"; /* Label in login screen to inform about the chat initialization proccess */ @@ -1385,7 +1383,7 @@ /* Notification text body shown when you have missed several audio calls and video calls. [A] = {number of missed audio calls}. [B] = {number of missed video calls} */ "missedAudioCallsAndMissedVideoCalls"="[A] llamadas perdidas y [B] videollamadas perdidas"; /* Button title to see the available bonus */ -"getBonus"="Obtén bonos"; +"getBonus"="Obtén bono"; /* Text shown under the title 'Video quality' that explains what it means */ "qualityOfVideosUploadedToAChat"="Calidad de los vídeos subidos a un chat"; /* Label used near to the option selected to encode the videos uploaded to a chat (Low, Medium, Original) */ @@ -1403,7 +1401,7 @@ /* Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible. */ "autoAccept"="Auto-aceptar"; /* Footer that explains the way Auto-Accept works for QR codes */ -"autoAcceptFooter"="Los contactos que escanean tu Código QR se añadirán automáticamente a tu lista de contactos."; +"autoAcceptFooter"="Los contactos que escanean tu código QR se añadirán automáticamente a tu lista de contactos."; /* Action to reset the current valid QR code of the user */ "resetQrCode"="Restablecer código QR"; /* Footer that explains what would happen if the user resets his/her QR code */ @@ -1491,7 +1489,7 @@ /* Title to confirm that you want to logout */ "proceedToLogout"="Cerrar la sesión"; /* Used as a message in the "Password reminder" dialog as a tip on why confirming the password and/or exporting the recovery key is important and vital for the user to not lose any data. */ -"testPasswordLogoutText"="Estás a punto de cerrar la sesión. Prueba tu contraseña para asegurarte de que la recuerdes. Si pierdes tu contraseña, perderás el acceso a tus datos en MEGA."; +"testPasswordLogoutText"="Estás a punto de cerrar la sesión. Prueba tu contraseña para asegurarte de que la recuerdas. Si pierdes tu contraseña, perderás el acceso a tus datos en MEGA."; /* state previous to import a file */ "loading"="Cargando…"; /* */ @@ -1515,13 +1513,13 @@ /* When an outgoing call of user A with user B had been rejected by user B */ "callWasRejected"="La llamada ha sido rechazada"; /* When an active call of user A with user B had cancelled */ -"callWasCancelled"="La llamada has sido cancelada"; +"callWasCancelled"="La llamada ha sido cancelada"; /* When an active call of user A with user B had not answered */ "callWasNotAnswered"="La llamada no ha sido respondida"; /* When an active call of user A with user B had failed */ "callFailed"="Llamada fallida"; /* When an active call of user A with user B had ended */ -"callEnded"="Fin de llamada."; +"callEnded"="Fin de llamada"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duración: %@"; /* */ @@ -1543,7 +1541,7 @@ /* Alert title shown when users try to stream an unsupported audio/video file */ "fileNotSupported"="Archivo no soportado"; /* Alert message shown when users try to stream an unsupported audio/video file */ -"message_fileNotSupported"="Puede guardarlo en Sin conexión y abrirlo con una aplicación compatible."; +"message_fileNotSupported"="Puedes guardarlo en Sin conexión y abrirlo con una aplicación compatible."; /* Item of a menu to forward a message chat to another chatroom */ "forward"="Reenviar"; /* Success message shown after forwarding messages to other chats */ @@ -1576,6 +1574,8 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="La autenticación de dos factores es una segunda capa de seguridad para tu cuenta. Esto significa que incluso si alguien conoce tu contraseña no va a poder acceder a tu cuenta sin tener acceso al código de seis dígitos al que solo tú tienes acceso."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Necesitas una aplicación de autenticación de dos factores en este dispositivo"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Necesitas una aplicación de autenticación para activar el 2FA en MEGA. Puedes descargar e instalar Google Authenticator, Duo Mobile, Authy o Microsoft Authenticator para tu teléfono o tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Autenticación de dos factores activada"; /* A message on the dialog shown after 2FA was successfully enabled. */ @@ -1590,17 +1590,17 @@ "scanOrCopyTheSeed"="Escanea o copia la clave en tu aplicación de autenticación. Asegúrate de guardarla en un lugar seguro en caso de que pierdas tu dispositivo."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="¿Has perdido tu dispositivo de autenticación?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Guardar imágenes en la galería"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Guardar vídeos en la galería"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Guardar imágenes en Fotos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Guardar vídeos en Fotos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Las imágenes y/o los videos descargados se almacenarán en la galería del dispositivo en lugar de la sección Sin conexión."; /* Setting associated with the 'Camera' of the device */ "Camera"="Cámara"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Guardar en la galería"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Guardar en Fotos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Guarda una copia de las imágenes y de los videos capturados desde la aplicación de MEGA en la galería del dispositivo."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Tú tienes las claves"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Permitir el acceso a Fotos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Para compartir fotos y videos, permite que MEGA acceda a tus fotos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Permite a MEGA que acceda a Fotos para compartir fotos y vídeos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Activar el micrófono y la cámara"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,13 +1635,13 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Nos gustaría enviarte notificaciones para que recibas nuevos mensajes al instante en tu dispositivo."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Habilitar acceso"; +"Allow Access"="Permitir acceso"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Gestión de archivos"; /* Title of the option to enable or disable file versioning on Settings section */ "File versioning"="Control de versiones de archivos"; /* Subtitle of the option to enable or disable file versioning on Settings section */ -"Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Activa o desactiva el control de versiones de archivos para toda su cuenta. \n Puede recibir versiones de archivos de carpetas compartidas si sus contactos lo tienen habilitado."; +"Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Activa o desactiva el control de versiones de archivos para toda tu cuenta. \nSeguirás teniendo versiones de archivos en las carpetas compartidas si tus contactos lo tienen activado."; /* A confirmation message when the user chooses to disable file versioning. */ "When file versioning is disabled, the current version will be replaced with the new version once a file is updated (and your changes to the file will no longer be recorded). Are you sure you want to disable file versioning?"="Si desactivas el control de versiones y subes un archivo, la versión nueva reemplazará la actual y los cambios no serán archivados como versiones. ¿Seguro que quieres desactivar el control de versiones?"; /* Settings preference title to show file versions info of the account */ @@ -1653,11 +1653,11 @@ /* Text of a button which deletes all historical versions of files in the users entire account. */ "Delete old versions"="Eliminar versiones anteriores"; /* A warning note about deleting all file versions in the settings section. */ -"All current files will remain. Only historic versions of your files will be deleted."="Los archivos actuales permanecerán. Solo se borrará el histórico de versiones de tus archivos."; +"All current files will remain. Only historic versions of your files will be deleted."="Los archivos actuales serán conservados. Solo se borrará el histórico de versiones de tus archivos."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Estás a punto de eliminar el historial de versiones de todos los archivos. Las versiones compartidas contigo por otros contactos las tendrán que borrar ellos.\n\nTen en cuenta que los archivos actuales no serán eliminados."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Limpieza automática de la Papelera:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Eliminar archivos anteriores a"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="La Papelera se puede vaciar de forma automática. El período mínimo es de 7 días."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Para desactivar la limpieza automática de la papelera o establecer un período de retención más largo, hazte PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="¿Quieres cerrar las demás sesiones? Te desconectarás de todas sesiones activas excepto la actual."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="¿Quieres cerrar las demás sesiones? Te desconectarás de todas sesiones activas excepto la actual."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Guardar en Fotos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Guardando en Fotos..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Guardado en Fotos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="El elemento no se ha podido guardar"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Ver como lista"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Vista en miniatura"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ se ha unido a la llamada."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ ha abandonado la llamada."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Mala conexión."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Llamada de grupo en curso. Toca para unirte."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Toca para volver a la llamada %@"; +/* Menu item to change from grid view to list view */ +"List view"="Ver como lista"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Vista en miniatura"; +/* There are no notifications to display. */ +"No notifications"="Sin notificaciones"; +/* The header of a notification related to payments */ +"Payment info"="Información de pago"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Tu plan PRO caducará en breve"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Notificación y retirada"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Retirada revocada"; +/* Label shown inside an unseen notification */ +"New"="Nueva"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Te envió una solicitud de contacto"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Ha cancelado su solicitud de contacto"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Recordatorio: tienes una solicitud de contacto"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Te ha eliminado de sus contactos"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Ahora ambos sois contactos"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="La cuenta ha sido eliminada/desactivada"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Te ha bloqueado como contacto"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Has ignorado una solicitud de contacto"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Has aceptado una solicitud de contacto"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Has denegado una solicitud de contacto"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Aceptar la solicitud de tu contacto"; +/* When somebody denied your contact request */ +"Denied your contact request"="Denegó tu solicitud de contacto"; +/* notification text */ +"A user has left the shared folder {0}"="Un usuario ha abandonado la carpeta compartida {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="El acceso a las carpetas ha sido eliminado."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Ha añadido 1 archivo"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Ha añadido %lld archivos"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Ha añadido 1 carpeta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Ha añadido %lld carpetas"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Ha añadido 1 archivo y 1 carpeta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Ha añadido %lld archivos y 1 carpeta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Ha añadido 1 archivo y %lld carpetas"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Ha añadido [A] archivos y [B] carpetas"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Se han eliminado [X] elementos compartidos"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Tu pago para el plan %1 ha sido recibido."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Tu pago para el plan %1 no se ha completado."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Tu plan PRO caducará en 1 día."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Tu plan PRO caducará en %1 días."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Tu %1 (%2) que has compartido públicamente ha sido eliminado/a."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="La eliminación de: %1 (%2) ha sido revocada."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Permitir que tus contactos vean tu último acceso a MEGA. Al desactivarlo no podrás ver el último acceso de tus contactos."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Mostrar"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Último acceso %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Último acceso hace mucho tiempo"; +/* */ +"Today"="Hoy"; \ No newline at end of file diff --git a/iMEGA/Languages/fr.lproj/Localizable.strings b/iMEGA/Languages/fr.lproj/Localizable.strings index ff73358e59..c31efd3cb1 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.strings +++ b/iMEGA/Languages/fr.lproj/Localizable.strings @@ -1,7 +1,7 @@ /* Button title to cancel something */ "cancel"="Annuler"; /* Button title to accept something */ -"ok"="D’accord"; +"ok"="Valider"; /* */ "yes"="Oui"; /* */ @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Vous ne pouvez pas télécharger un dossier vide"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Votre compte a déjà été activé. Veuillez vous connecter."; +"accountAlreadyConfirmed"="Votre compte a été activé. Veuillez vous connecter."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Lien invalide"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -61,7 +61,7 @@ /* Message shown when the app is waiting for the server to complete a request due to an API lock (error -3). */ "takingLongerThanExpected"="Le processus prend plus longtemps que prévu. Veuillez patienter."; /* Message shown when the app is waiting for the server to complete a request due to a HTTP error 500. */ -"serversAreTooBusy"="Les serveurs sont trop occupés. Veuillez patienter."; +"serversAreTooBusy"="Les serveurs sont trop occupés. Veuillez patienter…"; /* Message shown when the app is waiting for the server to complete a request due to connectivity issue. */ "unableToReachMega"="Impossible d’atteindre MEGA. Veuillez vérifier votre connectivité ou ressayer plus tard."; /* Message shown when the app is waiting for the server to complete a request due to a rate limit (error -4). */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Saisir une adresse courriel valide"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Le format de l'adresse courriel est invalide"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Saisir un mot de passe valide"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Adresse courriel"; @@ -130,10 +130,8 @@ "passwordStrong"="Ce mot de passe résistera à la plupart des attaques par force brute typiques. Veuillez vous assurer de vous en souvenir."; /* */ "agreeWithTheMEGATermsOfService"="J'accepte les conditions générales d'utilisation de MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Saisir un nom valide"; -/* */ -"invalidFirstNameAndLastName"="Prénom ou nom de famille invalide."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Les mots de passes ne correspondent pas"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="En attente de la confirmation par courriel."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Veuillez saisir votre mot de passe pour confirmer votre compte"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirmer votre compte"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirmer le compte"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Mot de passe erroné"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -265,9 +263,9 @@ /* A label to indicate a paused state for a transfer item (upload/download). */ "paused"="En pause"; /* Label for the state of a transfer when is being completing - (String as short as possible). */ -"Completing..."="Finalisation..."; +"Completing..."="Finalisation…"; /* Label for the state of a transfer when is being retrying - (String as short as possible). */ -"Retrying..."="Nouvel essai..."; +"Retrying..."="Nouvel essai…"; /* Section title of the 'Sort by' */ "sortTitle"="Trier par"; /* Button title to 'Save' the selected option */ @@ -299,7 +297,7 @@ /* A tooltip message which shows when a file name is duplicated during renaming. */ "There is already a file with the same name"="Un fichier du même nom existe déjà"; /* Option given on the `Add` section to allow the user upload something from another cloud storage provider. The three dots implies that an action occurs after tapping on it. */ -"uploadFrom"="Importer de..."; +"uploadFrom"="Importer de…"; /* Message shown when the user selects a file from another cloud storage provider that's already uploaded. "[A] = {name of the original file} already uploaded with name [B] = {name of the file in MEGA}" */ "fileExistAlertController_Message"="[A] est déjà téléversé avec le nom [B]"; /* Message shown when you try to upload one photo or video that is already uploaded in the current folder. “Already uploaded with name {File name} */ @@ -339,9 +337,9 @@ /* A menu item in the right click context menu in the Cloud Drive. This menu item will take the user to a dialog where they can manage the public folder/file links which they currently have selected. */ "manageLinks"="Gérer les liens"; /* Message shown when a link to a file or folder is being generated */ -"generatingLink"="Génération du lien..."; +"generatingLink"="Génération du lien…"; /* Message shown when some links to files and/or folders are being generated */ -"generatingLinks"="Génération des liens..."; +"generatingLinks"="Génération des liens…"; /* Message shown when the link has been copied to the pasteboard */ "linkCopied"="Le lien a été copié"; /* Message shown when some links have been copied to the pasteboard */ @@ -349,13 +347,13 @@ /* Text of the button after the links were copied to the clipboard */ "copiedToTheClipboard"="Copié vers le presse-papiers"; /* A title for the Copyright Warning dialog. */ -"copyrightWarning"="Avertissement de droits d’auteur"; +"copyrightWarning"="Avertissement de droit d’auteur"; /* A title for the Copyright Warning dialog. Designed to make the user feel as though this is not targeting them, but is a warning for everybody who uses our service. */ -"copyrightWarningToAll"="Avertissement sur les droits d'auteur à tous les utilisateurs"; +"copyrightWarningToAll"="Avertissement sur le droit d’auteur à tous les utilisateurs"; /* String already exists: 367, but we need to split paragraphs. */ -"copyrightMessagePart1"="MEGA respecte les droits d'auteur d'autrui et exige que les utilisateurs du service nuagique MEGA se conforment aux lois sur les droits d'auteur."; +"copyrightMessagePart1"="MEGA respecte le droit d’auteur d’autrui et exige que les utilisateurs du service nuagique MEGA se conforment aux lois sur le droit d’auteur."; /* String already exists: 367, but we need to split paragraphs */ -"copyrightMessagePart2"="Il vous est strictement interdit d'utiliser le service nuagique MEGA pour enfreindre les droits d'auteur. Vous ne pouvez pas téléverser, télécharger, stocker, partager, afficher, lire en continu, distribuer, envoyer par courriel, créer de lien vers, transmettre ou proposer de quelque façon des fichiers, des données ou du contenu qui enfreignent les droits d'auteur ou autre droit de propriété de quelque personne ou entité."; +"copyrightMessagePart2"="Il vous est strictement interdit d’utiliser le service nuagique MEGA pour enfreindre le droit d’auteur. Vous ne pouvez pas téléverser, télécharger, stocker, partager, afficher, lire en continu, distribuer, envoyer par courriel, créer de liens vers, transmettre ou proposer de quelque façon des fichiers, des données ou du contenu qui enfreignent le droit d’auteur ou autre droit de propriété de quelque personne ou entité."; /* button caption text that the user clicks when he agrees */ "agree"="Accepter"; /* button caption text that the user clicks when he disagrees */ @@ -367,7 +365,7 @@ /* An alert dialog for the Get Link feature */ "proOnly"="(PRO SEULEMENT)"; /* A label in the Get Link dialog which allows the user to set an expiry date on their public link. */ -"setExpiryDate"="Définir une date d'expiration"; +"setExpiryDate"="Définir une date d’expiration"; /* This is a title label on the Export Link dialog. The title covers the section where the user can password protect a public link. */ "setPasswordProtection"="Définir la protection par mot de passe"; /* The text of a button. This button will encrypt a link with a password. */ @@ -533,7 +531,7 @@ /* Alert title displayed when Camera Uploads will be disabled because of unexpected errors */ "cameraUploadsWillBeDisabled"="Le téléversement de la caméra sera désactivé"; /* Message shown when you have Camera Uploads enabled and your device don't have enough space to make it work */ -"cameraUploadsDisabled_alertView_message"="Le téléversement de la caméra sera désactivé, car vous n'avez pas assez d'espace sur votre appareil"; +"cameraUploadsDisabled_alertView_message"="Le téléversement de la caméra sera désactivé, car vous n’avez pas assez d’espace sur votre appareil"; /* Title of the Offline section */ "offline"="Hors ligne"; /* Title of the Contacts section */ @@ -597,7 +595,7 @@ /* Success message shown when some contacts have been invited */ "theUsersHaveBeenInvited"="Les utilisateurs ont été invités et apparaîtront dans votre liste de contacts s'ils acceptent."; /* Error message shown when it hasn't been posible to add a contact */ -"addContactError"="Erreur d'ajout de contact"; +"addContactError"="Erreur d’ajout de contact"; /* Alert title shown when you want to remove one or more contacts */ "removeUserTitle"="Supprimer le contact"; /* Alert confirmation message shown when you want to remove one contact from your contacts list */ @@ -611,7 +609,7 @@ /* Label shown when a contact request is pending */ "pending"="En attente"; /* Alert title shown when you add a contact from your device and the selected one doesn't have an email. */ -"contactWithoutEmail"="Le contact n'a pas d'adresse courriel"; +"contactWithoutEmail"="Le contact n’a pas d’adresse courriel"; /* Button title of the action that allows to leave a shared folder */ "leaveFolder"="Quitter"; /* Alert message shown when the user tap on the leave share action for one inshare */ @@ -733,7 +731,7 @@ /* Account closure, warning message to remind user to contact MEGA support after he confirms that he wants to cancel account. */ "ifYouCantAccessYourEmailAccount"="Si vous ne pouvez pas accéder à vos courriels veuillez contacter support@mega.nz"; /* During account cancellation (deletion) */ -"cancellationLinkHasExpired"="Le lien d'annulation a expiré."; +"cancellationLinkHasExpired"="Le lien d’annulation a expiré."; /* Text shown in the last alert dialog to confirm the cancellation of an account */ "enterYourPasswordToConfirmThatYouWanToClose"="Ceci est la dernière étape pour annuler votre compte. Vous perdrez irrémédiablement toutes les données stockées dans le nuage. Veuillez saisir votre mot de passe ci-dessous."; /* During account cancellation (deletion) */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="Aide"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Centre d'assistance"; +"helpCentreLabel"="Centre d’assistance"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Tester la version bêta"; /* Title to rate the app */ "rateUsLabel"="Évaluez-nous"; /* Title of the Transfers section */ @@ -922,11 +920,11 @@ /* */ "installMEGASync"="Installer MEGAsync"; /* Message shown on the achievements dialog for achieved achievements, %1 and %2 are replaced with e.g. 20 GB */ -"installMEGASyncCompletedExplanation"="Vous avez reçu %1 d'espace de stockage et %2 de quota de transfert pour avoir installé MEGAsync."; +"installMEGASyncCompletedExplanation"="Vous avez reçu %1 d’espace de stockage et %2 de quota de transfert pour avoir installé MEGAsync."; /* */ "installOurMobileApp"="Installer notre appli mobile"; /* Message shown on the achievements dialog for achieved achievements, %1 and %2 are replaced with e.g. 20 GB */ -"installOurMobileAppCompletedExplanation"="Vous avez reçu %1 d'espace de stockage et %2 de quota de transfert pour avoir installé notre appli mobile."; +"installOurMobileAppCompletedExplanation"="Vous avez reçu %1 d’espace de stockage et %2 de quota de transfert pour avoir installé notre appli mobile."; /* */ "xDaysLeft"="Il reste %1 jours"; /* %1 will be replaced by a numeric value and %2 will be 'days' or 'months', for example (Expires in [S]10[/S] days) */ @@ -956,7 +954,7 @@ /* Button title that goes to the section Usage where you can see how your MEGA space is used */ "usage"="Utilisation"; /* Title for "Local" used space. */ -"localLabel"="Local"; +"localLabel"="Localement"; /* Title for "Used" MEGA space. */ "usedSpaceLabel"="Utilisés"; /* Title for MEGA "Available" space. */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Vous pourrez surclasser votre compte ultérieurement dans Mon compte"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Votre disque nuagique est presque plein.[/S] [A]Passez maintenant[/A] à un compte PRO et obtenez [S]jusqu’à 8 To (8 192 Go)[/S] d’espace nuagique de stockage."; +"cloudDriveIsAlmostFull"="[S]Votre disque nuagique est presque plein.[/S] [A]Surclassez votre compte maintenant[/A] vers un compte PRO et obtenez [S]jusqu’à 8 To (8 192 Go)[/S] d’espace de stockage nuagique."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Votre compte est plein.[/S] [A]Passez maintenant[/A] à un compte PRO et obtenez [S]jusqu’à 8 To (8 192 Go)[/S] d’espace de stockage nuagique."; +"cloudDriveIsFull"="[S]Votre compte est plein.[/S] [A]Surclassez votre compte maintenant[/A] vers un compte PRO et obtenez [S]jusqu’à 8 To (8 192 Go)[/S] d’espace de stockage nuagique."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Espace de stockage"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -998,7 +996,7 @@ /* Price asociated with the MEGA PRO account level you can subscribe */ "productPricePerMonth"="%@ par mois"; /* Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue */ -"choosePlan"="Choisissez une des offres ci-dessous :"; +"choosePlan"="Choisissez un des abonnements ci-dessous :"; /* Text that explains that you have to select the plan you want and/or need after creating an account */ "selectOneAccountType"="Sélectionner un type de compte :"; /* Text shown in the purchase plan view to explain that annual subscription is 17% cheaper than 12 monthly payments */ @@ -1006,9 +1004,9 @@ /* Text shown on the upgrade account page above the current PRO plan subscription */ "currentPlan"="Abonnement actuel :"; /* Button on the Pro page to request a custom Pro plan because their storage usage is more than the regular plans. */ -"requestAPlan"="Demander une offre"; +"requestAPlan"="Demander un abonnement"; /* Asks the user to request a custom Pro plan from customer support because their storage usage is more than the regular plans. */ -"thereAreNoPlansSuitableForYourCurrentUsage"="Aucune offre ne correspond à votre utilisation actuelle. Veuillez [A]contacter l'assistance[/A] pour obtenir une offre personnalisée."; +"thereAreNoPlansSuitableForYourCurrentUsage"="Aucun abonnement ne correspond à votre utilisation actuelle. Veuillez [A]contacter l’assistance[/A] pour obtenir un abonnement personnalisé."; /* Header shown to help on the purchasin process */ "selectMembership"="Choisir un abonnement :"; /* Footer shown to remenber that if you select a yearly plan yo will save up to 17% */ @@ -1092,7 +1090,7 @@ /* A log message in the chat conversation to tell the reader that a participant [A] changed group chat name to [B]. Please keep the [A] and '[B]' placeholders, they will be replaced at runtime. For example: Alice changed group chat name to 'MEGA'. */ "changedGroupChatNameTo"="[A] a changé le nom de la conversation de groupe en '[B]'."; /* Button label. Allows to add contacts in current chat conversation. */ -"addParticipant"="Ajouter un participant..."; +"addParticipant"="Ajouter un participant…"; /* Menu item to add participants to a chat */ "addParticipants"="Ajouter des participants"; /* Title that refers to the status of the chat (Either Online or Offline) */ @@ -1206,13 +1204,13 @@ /* Alert message shown when a chat does not exist */ "chatNotFound"="La conversation est introuvable"; /* A typing indicator in the chat. Please leave the %@ which will be automatically replaced with the user's name at runtime. */ -"isTyping"="%@ écrit..."; +"isTyping"="%@ écrit…"; /* Plural, a hint that appears when two users are typing in a group chat at the same time. The parameter will be the concatenation of both user names. The tags and placeholders shouldn't be translated or modified. */ -"twoUsersAreTyping"="%1$s [A]écrivent...[/A]"; +"twoUsersAreTyping"="%1$s [A]écrivent…[/A]"; /* text that appear when there are more than 2 people writing at that time in a chat. For example User1, user2 and more are typing... The parameter will be the concatenation of the first two user names. The tags and placeholders shouldn't be translated or modified. */ -"moreThanTwoUsersAreTyping"="%1$s [A]et plus écrivent...[/A]"; +"moreThanTwoUsersAreTyping"="%1$s [A]et plus écrivent…[/A]"; /* This is shown in the typing area in chat, as a placeholder before the user starts typing anything in the field. The format is: Write a message to Contact Name... Write a message to "Chat room topic"... Write a message to Contact Name1, Contact Name2, Contact Name3 */ -"writeAMessage"="Rédiger un message pour %s..."; +"writeAMessage"="Rédiger un message pour %s…"; /* Information if there are no history messages in current chat conversation */ "noConversationHistory"="Aucun historique de conversation"; /* Button which allows to retry send message in chat conversation. */ @@ -1252,7 +1250,7 @@ /* Description shown when you almost had used your available transfer quota. */ "depletedTransferQuota_message"="Votre téléchargement en attente dépasse le quota de transfert actuel disponible de votre compte et pourrait par conséquent être interrompu."; /* Button title to see the available pro plans in MEGA */ -"seePlans"="Voir les offres"; +"seePlans"="Voir les abonnements"; /* Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible). */ "dismiss"="Fermer"; /* Notification text body shown when you have received a new chat message */ @@ -1286,11 +1284,11 @@ /* Label in chat rooms that indicates how many messages are unread. Plural and as short as possible. */ "unreadMessages"="%lu messages non lus"; /* Describe how works auto-renewable subscriptions on the Apple Store */ -"autorenewableDescription"="Les abonnements sont renouvelés automatiquement pour les périodes d’abonnement successives de même durée et au même tarif que la période initiale choisie. Vous pouvez désactiver le renouvellement automatique de votre abonnement MEGA PRO au plus tard 24 heures avant l’échéance de votre prochain paiement dans la page des réglages de votre compte iTunes. Pour gérer vos abonnements, cliquez simplement sur l’icône de la logithèque App Store sur votre appareil, connectez-vous en bas de la page avec votre identifiant Apple (si ce n’est pas déjà fait), et cliquez sur Afficher l’identifiant Apple. Vous atteindrez la page de votre compte où vous trouverez Gérer vers le bas. Ensuite, vous pourrez sélectionner votre abonnement MEGA PRO et visualiser votre date prévue de renouvellement, choisir un autre type d’abonnement, ou activer, désactiver le renouvellement automatique de votre abonnement."; +"autorenewableDescription"="Les abonnements sont renouvelés automatiquement pour les périodes d’abonnement successives de même durée et au même prix que la période initiale choisie. Vous pouvez désactiver le renouvellement automatique de votre abonnement MEGA PRO, au plus tard 24 heures avant l’échéance de votre prochain paiement, dans la page des réglages de votre compte iTunes. Pour gérer vos abonnements, cliquez simplement sur l’icône de la logithèque App Store sur votre appareil, connectez-vous en bas de la page avec votre identifiant Apple (si ce n’est pas déjà fait) et cliquez sur Afficher l’identifiant Apple. Vous atteindrez la page de votre compte où vous trouverez Gérer vers le bas. Ensuite, vous pourrez sélectionner votre abonnement MEGA PRO et visualiser votre date prévue de renouvellement, choisir un autre type d’abonnement, ou activer/ désactiver le renouvellement automatique de votre abonnement."; /* Text shown when you try to use a MEGA extension in iOS and you aren't logged */ "openMEGAAndSignInToContinue"="Ouvrir MEGA et se connecter pour continuer"; /* Label in login screen to inform about the chat initialization proccess */ -"connecting"="Connexion..."; +"connecting"="Connexion…"; /* Inform user that there were unsupported assets in the share extension */ "shareExtensionUnsupportedAssets"="Certains éléments n’ont pas pu être partagés avec MEGA"; /* Message shown to the user when the share extension is about to be killed by iOS due to a memory issue */ @@ -1299,7 +1297,7 @@ "Transfer failed:"="Échec de transfert :"; /* MEGAError */ /* Label to show that an SDK operation has been complete successfully. */ -"No error"="Pas d'erreur"; +"No error"="Aucune erreur"; /* Label to show that an Internal error occurs during a SDK operation. */ "Internal error"="Erreur interne"; /* Label to show that an error of Invalid argument occurs during a SDK operation. */ @@ -1339,11 +1337,11 @@ /* Label to show that an error related with too many connections occurs during a SDK operation. */ "Connection overflow"="Surcharge de la connexion"; /* Label to show that an error related with an write error occurs during a SDK operation. */ -"Write error"="Erreur d'écriture"; +"Write error"="Erreur d’écriture"; /* Label to show that an error related with an read error occurs during a SDK operation. */ "Read error"="Erreur de lecture"; /* Label to show that an error related with an invalid or missing application key occurs during a SDK operation. */ -"Invalid application key"="Clé d'application invalide"; +"Invalid application key"="Clé d’application invalide"; /* Label to show that an error related with an unknown error occurs during a SDK operation. */ "Unknown error"="Erreur inconnue"; /* Text shown in an alert when the user is about to change the language of the app */ @@ -1352,7 +1350,7 @@ "languageRestartNotification"="La langue a été changée. Toquez pour redémarrer."; /* The following strings are only for the App Store description, they're not used on the app (The only exception is the one with 'autorenewableDescription' key) On the 'generalDescriptionParagraph6' we must delete 'Desktop - https://mega.nz/' because the app can be rejected because of that. */ /* Paragraph 1 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph1"="MEGA offre un service de stockage nuagique chiffré contrôlé par l’utilisateur, en utilisant des navigateurs Web normaux, mais aussi des applis dédiées pour les appareils mobiles. Contrairement aux autres fournisseurs de service de stockage nuagique, vos données ne sont chiffrées et déchiffrées que par vos appareils client et jamais par nous."; +"generalDescriptionParagraph1"="MEGA offre un service de stockage nuagique chiffré contrôlé par l’utilisateur, en utilisant des navigateurs Web normaux, mais aussi des applis dédiées pour les appareils mobiles. Contrairement aux autres fournisseurs de services de stockage nuagique, vos données ne sont chiffrées et déchiffrées que par vos appareils client et jamais par nous."; /* Paragraph 2 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ "generalDescriptionParagraph2"="Téléversez vos fichiers de votre téléphone intelligent ou tablette, puis recherchez, stockez, téléchargez, lisez en continu, visualisez, partagez, renommez ou supprimez vos fichiers n’importe quand, de n’importe quel appareil, n’importe où. Partagez des dossiers avec vos contacts et suivez leurs modifications en temps réel."; /* Paragraph 3 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ @@ -1397,7 +1395,7 @@ /* Property associated with the first one made and not a copy. For example: Get the original video, without encoding. */ "original"="Original"; /* Label for the status of a transfer when is being preparing - (String as short as possible). */ -"preparing..."="Préparation..."; +"preparing..."="Préparation…"; /* QR Code label, used in Settings as title. String as short as possible */ "qrCode"="Code QR"; /* Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible. */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Échec d’appel"; /* When an active call of user A with user B had ended */ -"callEnded"="Fin d’appel."; +"callEnded"="L’appel est terminé"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Durée : %@"; /* */ @@ -1576,6 +1574,8 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="L’authentification à deux facteurs est un niveau supplémentaire de sécurité pour votre compte. Cela signifie que même si quelqu’un connaissait votre mot de passe, il ne pourrait pas y accéder sans avoir aussi le code à 6 chiffres que seul vous aurez."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Vous avez besoin d’une appli d’authentification à deux facteurs sur cet appareil"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Il vous faut une appli d’authentification pour activer l’A2F avec MEGA. Vous pouvez télécharger et installer l’appli Google Authenticator, Duo Mobile, Authy ou Microsoft Authenticator pour votre téléphone ou votre tablette."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="L’authentification à deux facteurs est activée"; /* A message on the dialog shown after 2FA was successfully enabled. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="L’authentification à deux facteurs est désactivée"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Lisez ou copiez la graine vers votre appli d’authentification. Assurez-vous de sauvegarder cette graine en lieu sûr au cas où vous perdriez votre téléphone."; +"scanOrCopyTheSeed"="Balayez ou copiez la graine vers votre appli d’authentification. Assurez-vous de sauvegarder cette graine en lieu sûr, au cas où vous perdriez votre appareil."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Avez-vous perdu votre appareil d’authentification ?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Enregistrer les images dans la médiathèque"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Enregistrer les vidéos dans la médiathèque"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Enregistrer les images dans Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Enregistrer les vidéos dans Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Les images et les vidéos téléchargées seront enregistrées dans la médiathèque de l’appareil au lieu de la section Hors ligne."; /* Setting associated with the 'Camera' of the device */ "Camera"="Appareil photo"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Enregistrer dans la médiathèque"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Enregistrer dans Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Enregistrer dans la médiathèque de votre appareil un exemplaire des images et des vidéos prises avec l’appli MEGA."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Vous détenez les clés"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Autoriser l’accès à Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Pour partager des photos et des vidéos, autorisez MEGA à accéder à vos photos et vidéos."; +"Please give the MEGA App permission to access Photos to share photos and videos."="Veuillez donner à l’appli MEGA l’autorisation d’accéder à Photos pour partager vos photos et vidéos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Activer le microphone et l’appareil photo"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Nous vous enverrons des notifications afin que vous receviez instantanément sur votre appareil les nouveaux messages."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Autoriser l’accès"; +"Allow Access"="Autoriser l’accès"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Gestion des fichiers"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Tous les fichiers actuels seront conservés. Seules les versions archivées de vos fichiers seront supprimées."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Vous êtes sur le point de supprimer les historiques de version de tous les fichiers. Tout fichier partagé avec vous par un contact devra être supprimé par ce contact.\n\nVeuillez noter que les fichiers actuels ne seront pas supprimés."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Programmateur de vidage de la corbeille :"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Supprimer les fichiers de plus de"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="La corbeille peut être vidée pour vous automatiquement. La période minimale est de 7 jours."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Pour désactiver le programmateur de vidage de la corbeille ou pour définir une période de rétention plus longue, vous devez détenir un abonnement PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Souhaitez-vous fermer toutes les autres sessions ? Cela vous déconnectera de toutes les autres sessions, sauf la session actuelle."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Souhaitez-vous fermer toutes les autres sessions ? Cela vous déconnectera de toutes les autres sessions, sauf la session actuelle."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Enregistrer dans Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Enregistrement dans Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Enregistrée dans Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Impossible d’enregistrer l’élément"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Vue en liste"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Vue en imagettes"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ s’est joint à l’appel."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ a quitté l’appel."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="La connexion est de mauvaise qualité."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Un appel de groupe est actif. Touchez pour vous y joindre."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touchez pour revenir à l’appel %@"; +/* Menu item to change from grid view to list view */ +"List view"="Vue en liste"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Vue en imagettes"; +/* There are no notifications to display. */ +"No notifications"="Aucune notification"; +/* The header of a notification related to payments */ +"Payment info"="Infos de paiement"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Votre abonnement PRO arrivera bientôt à expiration"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Avis de retrait"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Retrait rétabli"; +/* Label shown inside an unseen notification */ +"New"="Nouvelle"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Vous a envoyé une demande de contact"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="A annulé sa demande de contact"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Rappel : vous avez une demande de contact"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="vous a supprimé en tant que contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="La relation de contact est établie"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Le compte a été supprimé/désactivé"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="vous a bloqué en tant que contact"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Vous avez ignoré une demande de contact"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Vous avez accepté une demande de contact"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Vous avez refusé une demande de contact"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="A accepté votre demande de contact"; +/* When somebody denied your contact request */ +"Denied your contact request"="A refusé votre demande de contact"; +/* notification text */ +"A user has left the shared folder {0}"="Un utilisateur a quitté le dossier partagé {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="L'accès aux dossiers a été retiré."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1 fichier a été ajouté"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld fichiers ont été ajoutés"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1 dossier a été ajouté"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld dossiers ont été ajoutés"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1 fichier et 1 dossier ont été ajoutés"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="%lld fichiers et 1 dossier ont été ajoutés"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="1 fichier et %lld dossiers ont été ajoutés"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[A] fichiers [B] dossiers ont été ajoutés"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="[X] éléments ont été retirés d’un partage"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Votre paiement pour l’abonnement %1 a été reçu."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Votre paiement pour l’abonnement %1 a échoué."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Votre abonnement PRO arrivera à expiration dans 1 jour."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Votre abonnement PRO arrivera à expiration dans %1 jours."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Votre partage public %1 (%2) a été retiré."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Votre %1 (%2) retiré a été rétabli"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Permettre à vos contacts de voir l’heure de votre dernière activité sur MEGA. Si cette option est désactivée, vous ne pourrez pas voir l’état d’activité de vos contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Afficher"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Vu pour la dernière fois %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Vu pour la dernière fois il y a longtemps"; +/* */ +"Today"="Aujourd’hui"; \ No newline at end of file diff --git a/iMEGA/Languages/he.lproj/Localizable.strings b/iMEGA/Languages/he.lproj/Localizable.strings index ed24717ef6..6fa974a4f9 100644 --- a/iMEGA/Languages/he.lproj/Localizable.strings +++ b/iMEGA/Languages/he.lproj/Localizable.strings @@ -102,7 +102,7 @@ "emailInvalidFormat"="הכנס אימייל תקין"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="כתובת המייל איננה תקינה"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="הכנס סיסמא תקינה"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="אימייל"; @@ -130,10 +130,8 @@ "passwordStrong"="סיסמתך תעמוד מול רוב התקפות המתוחכמות של Brute-Force. אנא וודא כי הינך זוכר אותה."; /* */ "agreeWithTheMEGATermsOfService"="אני מסכים עם תנאי השימוש של MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="הכנס שם תקין"; -/* */ -"invalidFirstNameAndLastName"="שם פרטי או שם משפחה לא תקניים"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="סיסמאות לא תואמות"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="ממתין לאישור מהמייל."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="אנא הכנס את סיסמתך לאישור החשבון"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="אשר את חשבונך"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="אשר חשבון"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="סיסמא לא נכונה"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Call failed"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Call ended"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="מנהל קבצים"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="All current files will remain. Only historic versions of your files will be deleted."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="תזמון ניקוי פח האשפה:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="מחק קבצים הישנים מ-"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="צפייה כרשימה"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="צפייה בתמונות מזעריות"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="צפייה כרשימה"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="צפייה בתמונות מזעריות"; +/* There are no notifications to display. */ +"No notifications"="אין התראות"; +/* The header of a notification related to payments */ +"Payment info"="מידע על צורת התשלום"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="מנוי ה-PRO יפוג תוקף בקרוב"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="הודעה על הורדת תוכן"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="הודעה על העלאת קובץ מחדש"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="שלח לך הזמנה להתחברות"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="ביטל את בקשת איש הקשר שלהם"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="תזכורת: יש לך בקשת איש קשר"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="מחק אותך כאיש קשר"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="אתם כרגע מחוברים"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="החשבון בוטל/נמחק"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="חסם אותך כאיש קשר"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="התעלמת מבקשת איש קשר"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="אשרת בקשת איש קשר"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="לא אישרת בקשת איש קשר"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="אישר את ההזמנה שלך"; +/* When somebody denied your contact request */ +"Denied your contact request"="דחה את בקשת איש קשר שלך"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="אין הגישה לתיקייה"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="מחק [X] פריטים מהשיתוף"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="התשלום שלך עבור החשבון %1 התקבל."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="התשלום שלך עבור החשבון %1 נכשל."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="מנוי ה-PRO שלך יפוג תוקף בעוד יום אחד."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="מנוי ה-PRO שלך יפוג תוקף בעוד %1 ימים."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="ה%1 (%2) משותף שהורד, הועלה מחדש."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="ה%1 (%2) שהורד, הועלה מחדש."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="היום"; diff --git a/iMEGA/Languages/id.lproj/Localizable.strings b/iMEGA/Languages/id.lproj/Localizable.strings index 0b62bc2531..8de1136861 100644 --- a/iMEGA/Languages/id.lproj/Localizable.strings +++ b/iMEGA/Languages/id.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Anda tidak dapat mendownload folder kosong"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Akun anda telah aktif. Mohon masuk."; +"accountAlreadyConfirmed"="Akun anda telah di-aktifasi. Silahkan masuk akun anda"; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Link salah"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Masukan email yang benar"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Format alamat email tidak sah"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Masukan password yang benar"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Password ini akan menahan serangan brute-force yang paling rumit. Harap pastikan bahwa anda akan mengingat password anda."; /* */ "agreeWithTheMEGATermsOfService"="Saya setuju dengan Kebijakan Pelayanan MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Masukan nama yang benar"; -/* */ -"invalidFirstNameAndLastName"="Nama depan dan/atau nama belakang tidak valid"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Password berbeda"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Menunggu konfirmasi email."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Harap masukan password anda untuk mengkonfirmasi akun anda"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Konfirm akun anda."; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Konfirmasi akun"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Password salah"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="Pusat Bantuan"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Bergabunglah dengan Beta"; /* Title to rate the app */ "rateUsLabel"="Rate kami"; /* Title of the Transfers section */ @@ -1294,7 +1292,7 @@ /* Inform user that there were unsupported assets in the share extension */ "shareExtensionUnsupportedAssets"="Beberapa item tidak dapat di share dengan MEGA"; /* Message shown to the user when the share extension is about to be killed by iOS due to a memory issue */ -"Limited system resources are available when sharing items. Try uploading these files from within the MEGA app."="Limited system resources are available when sharing items. Try uploading these files from within the MEGA app."; +"Limited system resources are available when sharing items. Try uploading these files from within the MEGA app."="Sumber daya sistem yang terbatas tersedia saat berbagi item. Coba unggah file-file ini dari dalam aplikasi MEGA."; /* Notification message shown when a transfer failed. Keep colon. */ "Transfer failed:"="Gagal transfer:"; /* MEGAError */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Autentikasi Dua Faktor adalah lapisan keamanan kedua untuk akun Anda. Yang berarti bahwa bahkan jika seseorang mengetahui kata sandi Anda, mereka tidak dapat mengaksesnya, tanpa juga memiliki akses ke kode enam digit yang hanya Anda miliki aksesnya."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Anda memerlukan aplikasi autentikasi dua faktor di perangkat ini"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Anda memerlukan aplikasi authenticator untuk mengaktifkan 2FA di MEGA. Anda dapat mengunduh dan memasang aplikasi Google Authenticator, Duo Mobile, Authy, atau Microsoft Authenticator untuk ponsel atau tablet Anda."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Otentikasi Dua Faktor Diaktifkan"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Lain kali Anda login ke akun Anda, Anda akan diminta memasukkan kode 6-digit yang disediakan oleh aplikasi otentikasi Anda."; +"twoFactorAuthenticationEnabledDescription"="Lain kali Anda login ke akun Anda, Anda akan diminta memasukkan kode 6-digit yang disediakan oleh aplikasi Otentikasi Anda."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Silakan simpan Recovery Key anda di lokasi yang aman"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1590,62 +1590,62 @@ "scanOrCopyTheSeed"="Pindai atau salin benih ke Aplikasi Authenticator Anda. Pastikan untuk mencadangkan benih ini ke tempat yang aman jika Anda kehilangan ponsel Anda."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Kehilangan perangkat Authenticator Anda?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Simpan Gambar di Foto"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Simpan Video di Foto"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ -"Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; +"Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Gambar dan / atau video yang diunduh akan disimpan di pustaka media perangkat, bukan bagian Offline."; /* Setting associated with the 'Camera' of the device */ -"Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ -"Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; +"Camera"="Kamera"; +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Simpan dalam Foto"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ +"Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Simpan salinan gambar dan video yang diambil dari aplikasi MEGA di pustaka media perangkat Anda."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ -"You hold the keys"="You hold the keys"; +"You hold the keys"="Anda memegang kunci"; /* Description shown in a page of the onboarding screens explaining the encryption paradigm */ -"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."; +"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="Keamanan adalah alasan kami ada, file Anda aman bersama kami di belakang mesin enkripsi yang diminyaki di mana hanya Anda yang dapat mengakses file Anda."; /* Title shown in a page of the on boarding screens explaining that the chat is encrypted */ -"Encrypted chat"="Encrypted chat"; +"Encrypted chat"="Chat terenkripsi"; /* Description shown in a page of the onboarding screens explaining the chat feature */ -"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."="Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."; +"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."="Obrolan yang sepenuhnya terenkripsi dengan panggilan suara dan video, perpesanan grup, dan integrasi berbagi file dengan Cloud Drive Anda."; /* Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate */ -"Create your Network"="Create your Network"; +"Create your Network"="Buat Jaringan Anda"; /* Description shown in a page of the onboarding screens explaining contacts */ -"Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"="Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"; +"Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"="Tambahkan kontak, buat jaringan, berkolaborasi, buat panggilan suara dan video tanpa harus meninggalkan MEGA"; /* Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically */ -"Your Photos in the Cloud"="Your Photos in the Cloud"; +"Your Photos in the Cloud"="Foto Anda di Cloud"; /* Description shown in a page of the onboarding screens explaining the camera uploads feature */ -"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."; +"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Unggahan Kamera adalah fitur penting untuk perangkat seluler apa pun dan kami telah melindungi Anda. Buat akun Anda sekarang."; /* Button which triggers the initial setup */ -"Setup MEGA"="Setup MEGA"; +"Setup MEGA"="Siapkan MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ -"To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +"To fully take advantage of your MEGA account we need to ask you some permissions."="Untuk sepenuhnya memanfaatkan akun MEGA Anda, kami perlu menanyakan beberapa izin."; /* Title label that explains that the user is going to be asked for the photos permission */ -"Allow Access to Photos"="Allow Access to Photos"; +"Allow Access to Photos"="Izinkan Akses ke Foto"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Tolong beri izin MEGA App untuk mengakses Foto untuk berbagi foto dan video."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ -"Enable Microphone and Camera"="Enable Microphone and Camera"; +"Enable Microphone and Camera"="Aktifkan Mikrofon dan Kamera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ -"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"; +"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="Untuk membuat panggilan suara dan video terenkripsi, izinkan akses MEGA ke Kamera dan Mikrofon Anda"; /* Title label that explains that the user is going to be asked for the notifications permission */ "Enable Notifications"="Aktifkan Notifikasi"; /* Detailed explanation of why the user should give permission to deliver notifications */ -"We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; +"We would like to send you notifications so you receive new messages on your device instantly."="Kami ingin mengirimkan pemberitahuan kepada Anda sehingga Anda menerima pesan baru di perangkat Anda secara instan."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Izinkan Akses"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Pengelolaan File"; /* Title of the option to enable or disable file versioning on Settings section */ -"File versioning"="File versioning"; +"File versioning"="Versi file"; /* Subtitle of the option to enable or disable file versioning on Settings section */ -"Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Enable or disable file versioning for your entire account.\nYou may still receive file versions from shared folders if your contacts have this enabled."; +"Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Aktifkan atau nonaktifkan versi file untuk seluruh akun Anda.\n Anda masih dapat menerima versi file dari folder bersama jika kontak Anda telah mengaktifkan ini."; /* A confirmation message when the user chooses to disable file versioning. */ "When file versioning is disabled, the current version will be replaced with the new version once a file is updated (and your changes to the file will no longer be recorded). Are you sure you want to disable file versioning?"="Ketika versi file dinonaktifkan, versi saat ini akan diganti dengan versi baru setelah file diperbarui (dan perubahan anda pada file tidak akan dicatat lagi). Yakin ingin menonaktifkan versi file?"; /* Settings preference title to show file versions info of the account */ -"File versions"="File versions"; +"File versions"="Versi file"; /* A title message in the user’s account settings for showing the storage used for file versions. */ "Total size taken up by file versions:"="Ukuran total diambil oleh versi file:"; /* The title of the section about deleting file versions in the settings. */ @@ -1655,9 +1655,9 @@ /* A warning note about deleting all file versions in the settings section. */ "All current files will remain. Only historic versions of your files will be deleted."="Ini akan menghapus secara permanen semua versi file Anda sendiri. Setiap versi file yang dibagikan kepada Anda dari kontak harus dihapus oleh mereka."; /* Text of the dialog to delete all the file versions of the account */ -"You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +"You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Anda akan menghapus riwayat versi semua file. Versi file apa pun yang dibagikan kepada Anda dari kontak harus dihapus oleh mereka.\n\nHarap dicatat bahwa file saat ini tidak akan dihapus."; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Scheduler Pembersih Sampah:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Hapus file lebih lama dari"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Sampah dapat dibersihkan untuk Anda secara otomatis. Jangka waktu minimum adalah 7 hari."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Untuk menonaktifkan Penjadwalan Pembersihan Rubbish-Bin atau menetapkan periode retensi yang lebih lama, Anda harus berlangganan ke rencana PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Apakah Anda ingin menutup semua sesi lainnya? Ini akan mengeluarkan Anda di semua sesi aktif lainnya kecuali sesi saat ini."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Apakah Anda ingin menutup semua sesi lainnya? Ini akan mengeluarkan Anda di semua sesi aktif lainnya kecuali sesi saat ini."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Simpan ke Foto"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Menyimpan ke Foto ..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Disimpan ke Foto"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Tidak dapat menyimpan Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="List view"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Thumbnail view"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="List view"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Thumbnail view"; +/* There are no notifications to display. */ +"No notifications"="Tidak ada notifikasi"; +/* The header of a notification related to payments */ +"Payment info"="Info pembayaran"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Membership PRO akan habis dalam waktu dekat"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Pemberitahuan pencopotan"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Pencopotan dikembalikan"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Telah mengirim anda request kontak"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Batalkan request kontak mereka"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Peringatan: anda memiliki request kontak"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Menghapus anda dari kontak"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Anda sekarang masuk dalam kontak"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Akun telah dihapus/ di non-aktifkan"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Mem-blok anda dari kontak"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Anda tidak mengindahkan request kontak"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Anda menerima request kontak"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Anda menolak request kontak"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Menerima request kontak anda"; +/* When somebody denied your contact request */ +"Denied your contact request"="Menolak request kontak anda"; +/* notification text */ +"A user has left the shared folder {0}"="Seorang pengguna telah meninggalkan folder bersama {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Akses ke folder telah dihapus."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Menghapus [X] item dari berbagi"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Pembayaran anda untuk plan %1 telah diterima."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Pembayaran anda untuk plan %1 tidak berhasil."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Membership PRO anda akan habis dalam 1 hari."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Membership PRO anda akan habis dalam %1 hari."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Anda punya %1 (%2) telah dicopot."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Pencopotan %1 (%2) telah dikembalikan."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Hari ini"; \ No newline at end of file diff --git a/iMEGA/Languages/it.lproj/Localizable.strings b/iMEGA/Languages/it.lproj/Localizable.strings index 1c8b6d7f2a..a1ac6020ba 100644 --- a/iMEGA/Languages/it.lproj/Localizable.strings +++ b/iMEGA/Languages/it.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Non è possibile scaricare una cartella vuota"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Il tuo account è già stato attivato. Effettua il Log in."; +"accountAlreadyConfirmed"="Il tuo account è già stato attivato. Per favore, effettua il login."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Link non valido"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Inserisci una mail valida"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Sembra che si tratti di una e-mail non valida"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Inserisci una password valida"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Questa password può resistere agli attacchi più tipici di brute-force. Assicurati di ricordarla."; /* */ "agreeWithTheMEGATermsOfService"="Sono d'accordo con i Termini di Servizio di Mega"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Inserisci un nome valido"; -/* */ -"invalidFirstNameAndLastName"="Nome e/o cognome non validi."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Le password non combaciano"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="In attesa della conferma dell'email."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Si prega di inserire la password per confermare il tuo account"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Conferma il tuo account"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Conferma account"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Password errata"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="Aiuto"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Servizio Clienti"; +"helpCentreLabel"="Assistenza"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Entra nella beta"; /* Title to rate the app */ "rateUsLabel"="Votaci"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Puoi passare a PRO più tardi in Il Mio Account"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Il tuo Cloud è quasi pieno.[/S] [A]Passa a PRO ora[/A] e ottieni [S]fino a 4TB (4096GB)[/S] di spazio cloud."; +"cloudDriveIsAlmostFull"="[S]Il tuo Cloud è quasi pieno.[/S] [A]Passa a PRO ora[/A] e ottieni [S]fino a 8TB (8192GB)[/S] di spazio cloud."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Il tuo account è pieno.[/S] [A]Passa a PRO ora[/A] e ottieni [S]fino a 4TB (4096 GB)[/S] di spazio cloud."; +"cloudDriveIsFull"="[S]Il tuo account è pieno.[/S] [A]Passa a PRO ora[/A] e ottieni [S]fino a 8TB (8192 GB)[/S] di spazio cloud."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Archivio"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="L'autenticazione a due fattori è un secondo livello di sicurezza per il tuo account.Che significa che anche se qualcuno sa la tua password non può accedere al tuo account, senza avere accesso anche al numero di 6 cifre a cui hai accesso solo tu."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Hai bisogno di un'applicazione per l'autenticazione a due fattori su questo dispositivo"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Hai bisogno di un'applicazione di autenticazione per attivare l'autenticazione a due fattori (2FA) su MEGA. Puoi scaricare ed installare le app di Google Authenticator, Duo Mobile, Authy o Microsoft Authenticator per il tuo telefono o tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Autenticazione a due fattori attivata"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="La prossima volta che effettuerai l'accesso al tuo account ti verrà chiesto di inserire il codice a 6 cifre fornito dalla tua app di autenticazione."; +"twoFactorAuthenticationEnabledDescription"="La prossima volta che effettuerai l'accesso al tuo account ti verrà chiesto di inserire il codice di 6 cifre che ti è stato fornito dalla app di autenticazione."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Per favore, salva la tua Chiave di Recupero in una posizione sicura"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Autenticazione a due fattori disabilitata"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scansiona o copia il seed della tua app di autenticazione. Sii sicuro di copiare questo seed in una posizione sicura nel caso in cui tu perda il tuo telefono."; +"scanOrCopyTheSeed"="Scansiona o copia il seed della tua app di autenticazione. Sii sicuro di copiare questo seed in una posizione sicura nel caso in cui tu perda il tuo dispositivo."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Hai perso il tuo dispositivo autenticatore?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Salva immagini nella Galleria"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Salva video nella Galleria"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Salva immagini nella Galleria"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Salva video nella Galleria"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Le immagini e/o i video scaricati saranno archiviati nella Galleria del dispositivo invece della sezione Offline."; /* Setting associated with the 'Camera' of the device */ "Camera"="Fotocamera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Salva nella Galleria"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Salva nella Galleria"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Salva una copia delle immagini e dei video fatti dall'app di MEGA nella Galleria del tuo dispositivo."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Tu hai le chiavi"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Permetti l'accesso alle foto"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Per condividere le foto e i video, permetti a MEGA di accedere alle tue foto"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Per favore, dai all'app di MEGA il permesso di accedere alla tua Galleria per salvare le foto e i video."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Attiva microfono e fotocamera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Vorremmo inviarti notifiche affinché tu possa ricevere messaggi sul tuo dispositivo immediatamente."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Attiva accesso"; +"Allow Access"="Attiva accesso"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Gestione file"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Tutti i file correnti rimarranno. Solo le versioni precedenti dei tuoi file verranno eliminate."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Stai per eliminare la storia delle versioni di tutti i file. Qualsiasi versione di un file condiviso con te da un contatto dovrà essere eliminato da quel contatto.\n\nPer favore, nota che i file correnti non saranno eliminati."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Schedulazione Svuotamento Cestino:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Rimuovi file più vecchi di"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Il Cestino può essere svuotato per te automaticamente. Il periodo minimo è di 7 giorni."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Per disabilitare il calendario di pulizia automatica del Cestino o impostare un periodo di ritenzione, devi abbonarti ad un piano PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Vuoi chiudere tutte le altre sessioni? Questo ti farà uscire da tutte le altre sessioni attive, fatta eccezione per quella corrente."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Vuoi chiudere tutte le altre sessioni? Questo ti farà uscire da tutte le altre sessioni attive, fatta eccezione per quella corrente."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Salva nella Galleria"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Salvataggio nella Galleria..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Salvata nella Galleria"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Non ho potuto salvare l'oggetto"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Modalità lista"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Vista thumbnail"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ si è unito alla chiamata."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ ha abbandonato la chiamata."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Connessione debole."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="C'è una chiamata di gruppo attiva. Clicca per unirti."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Tocca per tornare alla chiamata %@"; +/* Menu item to change from grid view to list view */ +"List view"="Modalità lista"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Vista thumbnail"; +/* There are no notifications to display. */ +"No notifications"="Nessuna notifica"; +/* The header of a notification related to payments */ +"Payment info"="Informazioni di pagamento"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Il piano di iscrizione PRO scadrà a breve"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Avviso di chiusura"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Chiusura annullata"; +/* Label shown inside an unseen notification */ +"New"="Nuovo"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Ti ha inviato una richiesta di contatto"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Annullato la loro richiesta di contatto"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Promemoria: Hai una richiesta di contatto"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Ti ha eliminato come contatto"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Relazione di contatto stabilita"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="L'account è stato cancellato/disattivato"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Ti ha bloccato come contatto"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Hai ignorato una richiesta di contatto"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Hai accettato una richiesta di contatto"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Hai negato una richiesta di contatto"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Ha accettato tua richiesta di contatto"; +/* When somebody denied your contact request */ +"Denied your contact request"="Richiesta di contatto negata"; +/* notification text */ +"A user has left the shared folder {0}"="Un utente ha lasciato la cartella condivisa {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="L'accesso alle cartelle è stato rimosso."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1 file aggiunto"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld file aggiunti"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1 cartella aggiunta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld cartelle aggiunte"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1 file e 1 cartella aggiunta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="%lld file e 1 cartella aggiunta"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="1 file e %lld cartelle aggiunte"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[A] file e [B] cartelle aggiunte"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Rimossi [X] oggetti dalla condivisione"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Il pagamento per il piano %1 è stato ricevuto."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="I pagamento per il piano %1 non ha avuto successo."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Il tuo piano di iscrizione PRO scadrà tra 1 giorno."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Il tuo piano di iscrizione PRO scadrà tra %1 giorni."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Il/la tuo/a %1 condiviso/a pubblicamente (%2) è stato/a chiuso/a."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Il/la tuo/a %1 (%2) chiuso/a è stato/a riattivato/a."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Permetti ai tuoi contatti di vedere l'ultima volta in cui sei stato attivo su MEGA. Se disattivato, non potrai vedere lo status di attività dei tuoi contatti."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Mostra"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Ultima visita %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Ultima visita molto tempo fa"; +/* */ +"Today"="Oggi"; \ No newline at end of file diff --git a/iMEGA/Languages/ja.lproj/Localizable.strings b/iMEGA/Languages/ja.lproj/Localizable.strings index 0f7a247061..5b639be343 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.strings +++ b/iMEGA/Languages/ja.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="空のフォルダをダウンロードすることができません。"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="あなたのアカウントはすでに作成されています。ログインしてください。"; +"accountAlreadyConfirmed"="あなたのアカウントは有効になっています。ログインしてください。"; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="リンク無効"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="正しいメールを入力して下さい"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="メールアドレスのフォーマットが正しくありません。"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="有効パスワードを入力して下さい"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="メール"; @@ -130,10 +130,8 @@ "passwordStrong"="このパスワードは大抵の攻撃に対抗できるでしょう。絶対に忘れないようにして下さい。"; /* */ "agreeWithTheMEGATermsOfService"="私は、MEGAの サービス規約に同意します。"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="正しい名前を入力して下さい"; -/* */ -"invalidFirstNameAndLastName"="名前が無効です。"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="パスワードが合いません。"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="少々お待ちください。メール確認が届きます。"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="アカウント確認のため、パスワードを入力して下さい。"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="アカウントを確認"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="アカウントを確認"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="パスワードが違います。"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="ヘルプセンター"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="ベータに参加"; /* Title to rate the app */ "rateUsLabel"="評価してください"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="マイアカウントで後でアップグレードできます"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]クラウドドライブはほぼいっぱいです。[/S]PROアカウントに[A]今すぐアップグレード[/A]し、[S]最大4 TB(4096 GB)[/S]のクラウドストレージスペースを取得してください。"; +"cloudDriveIsAlmostFull"="[S]クラウドドライブはほぼいっぱいです。[/S]PROアカウントに[A]今すぐアップグレード[/A]し、[S]最大8TB(8192GB)[/S]のクラウドストレージスペースを取得してください。"; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]あなたのアカウントはいっぱいです。[/S]PROアカウントに[A]今すぐアップグレード[/A]し、[S]最大4 TB(4096 GB)[/S]のクラウドストレージスペースを取得してください。"; +"cloudDriveIsFull"="[S]あなたのアカウントはいっぱいです。[/S]PROアカウントに[A]今すぐアップグレード[/A]し、[S]最大8TB(8192GB)[/S]のクラウドストレージスペースを取得してください。"; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="ストレージ"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="セットアップを開始"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="オーセンティケータアプリで生成された6桁のコードを入力してください。"; +"pleaseEnterTheSixDigitCode"="オーセンティケータアプリで生成された6桁のコードをご入力ください。"; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="なぜ二要素認証が必要ですか? "; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="二要素認証は、アカウント用のセキュリティの第2層です。つまり、たとえ誰かがあなたのパスワードを知っていても、あなただけがアクセスできる6桁のコードにアクセスできない限り、それにアクセスすることはできません。"; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="この端末では二要素認証アプリが必要です"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="MEGAで2FAを有効にするには、オーセンティケーターアプリが必要です。スマートフォンやタブレット用に、Googleオーセンティケータ、Duo Mobile、Authy、Microsoftオーセンティケータアプリをダウンロードしてインストールできます。"; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="二要素認証が有効になりました"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="次回アカウントにログインすると、認証アプリで提供される6桁のコードを入力するよう求められます。"; +"twoFactorAuthenticationEnabledDescription"="次回アカウントにログインすると、オーセンティケータアプリで提供される6桁のコードを入力するよう求められます。"; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="回復キーは安全な場所に保存しておいてください"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="二要素認証が無効になりました"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="シードをオーセンティケータアプリにスキャンまたはコピーします。携帯電話を失くした場合に備えて、このシードを安全な場所にバックアップしてください。"; +"scanOrCopyTheSeed"="シードをオーセンティケータアプリにスキャンまたはコピーします。端末を失くした場合に備えて、このシードを安全な場所に確実にバックアップしてください。"; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="オーセンティケータ端末を紛失しましたか? "; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="ライブラリに画像を保存"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="ライブラリに動画を保存"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="画像を写真に保存"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="動画を写真に保存"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="ダウンロードした画像や動画は、オフラインセクションではなく、端末のメディアライブラリに保存されます。"; /* Setting associated with the 'Camera' of the device */ "Camera"="カメラ"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="ライブラリに保存"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="写真に保存"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="MEGAアプリから取得した画像と動画のコピーをあなたの端末のメディアライブラリに保存します。"; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="あなたがキーを持っています"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="写真へのアクセスを許可する"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="写真や動画を共有するには、MEGAがあなたの写真へアクセスするのを許可してください"; +"Please give the MEGA App permission to access Photos to share photos and videos."="写真や動画を共有するために、MEGAアプリに写真へのアクセスを許可してください。"; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="マイクとカメラを有効にする"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="あなたに通知を送信し、新しいメッセージを瞬時に端末で受信いただけるようにしたいと思います。"; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="アクセスを有効にする"; +"Allow Access"="アクセスを許可"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="ファイル管理"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="現在のファイルはすべて残ります。あなたのファイルの過去バージョンのみが削除されます。"; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="すべてのファイルのバージョン履歴を削除しようとしています。連絡先があなたと共有しているファイルバージョンはいずれも、連絡先が削除する必要があります。\n\n現在のファイルは削除されませんのでご注意ください。"; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="ゴミ箱クリア管理:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="ファイル失効日は"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="ごみ箱は自動的にきれいにできます。最小期間は7日間です。"; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="ごみ箱クリーニングスケジューラを無効にするか、より長い保存期間を設定するには、PROプランをご購読いただく必要がございます。"; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="他のセッションをすべて終了しますか? これにより、現在のセッション以外のすべてのアクティブなセッションでログアウトされます。"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="他のセッションをすべて終了しますか? これにより、現在のセッション以外のすべてのアクティブなセッションでログアウトされます。"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="写真に保存"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="写真に保存中…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="写真に保存されました"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="項目を保存できませんでした"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="リスト一覧"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="サムネールで見る"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@さんが通話に参加しました。"; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@さんが通話から抜けました。"; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="接続不良。"; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="有効なグループ通話があります。タップして参加します。"; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="タッチして通話に戻る %@"; +/* Menu item to change from grid view to list view */ +"List view"="リスト一覧"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="サムネールで見る"; +/* There are no notifications to display. */ +"No notifications"="通知はありません"; +/* The header of a notification related to payments */ +"Payment info"="支払いに関して"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PROメンバーシッププランが間もなく失効します"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="テイクダウン通知"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="削除が復活しました"; +/* Label shown inside an unseen notification */ +"New"="新"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="知り合いリクエストを送りました。"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="知り合いリクエストをキャンセルしました。"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="お知らせ: 知り合いからのリクエストがあります。"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="あなたは削除されました"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="知り合い関係設立"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="アカウントは削除され、無効となりました。"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="あなたはブロックされました"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="知り合いのリクエストを無視しました。"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="知り合いリクエストを受け入れました。"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="知り合いのリクエストを拒否しました。"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="知り合いリクエストが受け入れられました。"; +/* When somebody denied your contact request */ +"Denied your contact request"="知り合いリクエストは拒否されました。"; +/* notification text */ +"A user has left the shared folder {0}"="あるユーザーが共有フォルダ{0}から去りました"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="フォルダのアクセス接続が削除されました。"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1個のファイルが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld個のファイルが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1個のフォルダが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld個のフォルダが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1個のファイルと1個のフォルダが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="%lld個のファイルと1個のフォルダが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="1個のファイルと%lld個のフォルダが追加されました"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[A]個のファイルと[B]個のフォルダが追加されました"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="共有から削除された[X]項目"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="%1 プランのお支払い、受領いたしました。"; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="%1プランのお支払いは受領できませんでした。"; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="あなたのPROメンバーシッププランは1日後に失効します。"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="あなたのPROメンバーシッププランは%1日後に失効します。"; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="あなたの公開共有された%1(%2)は削除されました。"; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="あなたの削除された%1(%2)は復活しました。"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="あなたの連絡先がMEGAであなたが最後に活動していた時間を見れるようにします。無効にすると、あなたの連絡先の活動状態を見られなくなります。"; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="表示"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="最終ログイン%s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="最終ログインはだいぶ前"; +/* */ +"Today"="今日"; \ No newline at end of file diff --git a/iMEGA/Languages/ko.lproj/Localizable.strings b/iMEGA/Languages/ko.lproj/Localizable.strings index eda7c3a637..ae32481962 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.strings +++ b/iMEGA/Languages/ko.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="빈 폴더를 다운로드할 수 없습니다"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="당신의 계정은 이미 활성화되었습니다. 로그인 하십시오."; +"accountAlreadyConfirmed"="당신의 계정은 활성화되었습니다. 로그인 하세요."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="유효하지 않은 링크"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="올바른 이메일을 입력하세요"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="이메일 주소 형식이 올바르지 않습니다"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="올바른 비밀번호를 입력하세요"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="이메일"; @@ -130,10 +130,8 @@ "passwordStrong"="이 비밀번호는 대부분의 정교한 무한 대입 공격을 버틸 것입니다. 확실히 기억해두세요."; /* */ "agreeWithTheMEGATermsOfService"="MEGA의 이용약관에 동의합니다"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="올바른 이름을 입력하세요"; -/* */ -"invalidFirstNameAndLastName"="이름 또는 성이 잘못 되었습니다."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="비밀번호가 일치하지 않습니다"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="이메일 확인 기다리는 중"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="계정을 확인하려면 비밀번호를 입력하세요"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="당신의 계정을 확인하세요"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="계정 확인"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="잘못된 비밀번호"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -806,7 +804,7 @@ /* Headline for parking an account (basically restarting from scratch) */ "parkAccount"="계정 보류"; /* Label text of a checkbox to ensure that the user is aware that the data of his current account will be lost when proceeding unless they remember their password or have their master encryption key (now renamed "Recovery Key") */ -"startingFreshAccount"="나는 새로운, 빈 계정을 시작하고 내가 비밀번호를 다시 떠올리거나 내보낸 복구 키의 위치를 찾아낼 때 까지 현재 계정의 모든 데이터를 잃어버린다는 것을 알고 있습니다."; +"startingFreshAccount"="나는 비어있는 새로운 계정을 시작하고, 내가 비밀번호를 다시 떠올리거나 저장된 복구 키의 위치를 찾아내지 못한다면 현재 계정의 모든 데이터를 잃어버린다는 것을 알고 있습니다."; /* Caption of the button to proceed */ "startNewAccount"="새 계정을 시작"; /* */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="도움 센터"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="베타 참여"; /* Title to rate the app */ "rateUsLabel"="평가"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="나중에 내 계정에서 업그레이드할 수 있습니다"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]클라우드 드라이브가 거의 찼습니다.[/S] PRO 계정으로 [A]지금 업그레이드[/A]하고 [S]최대 4 TB (4096 GB)[/S]의 클라우드 저장 공간을 받으세요."; +"cloudDriveIsAlmostFull"="[S]클라우드 드라이브가 거의 찼습니다.[/S] PRO 계정으로 [A]지금 업그레이드[/A]하고 [S]최대 8 TB (8192 GB)[/S]의 클라우드 저장 공간을 받으세요."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]계정이 가득 찼습니다.[/S] PRO 계정으로 [A]지금 업그레이드[/A]하고 [S]최대 4TB (4096 GB)[/S]의 클라우드 저장 공간을 얻으세요."; +"cloudDriveIsFull"="[S]계정이 가득 찼습니다.[/S] PRO 계정으로 [A]지금 업그레이드[/A]하고 [S]최대 8 TB (8192 GB)[/S]의 클라우드 저장 공간을 얻으세요."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="저장소"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1198,7 +1196,7 @@ /* Title show above the name of the persons with whom you're chatting */ "chattingWith"="대화중인 사람"; /* Full text: MEGA protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: Confidentiality - Only the author and intended recipients are able to decipher and read the content. Authenticity - There is an assurance that the message received was authored by the stated sender, and its content has not been tampered with during transport or on the server. */ -"chatIntroductionMessage"="MEGA는 당신의 대화 근본적 안전 보증을 제공하는 종단간 (이용자가 통제하는) 암호화를 통해 보호합니다."; +"chatIntroductionMessage"="MEGA는 당신의 대화 근본적 안전 보증을 제공하는 종단간 (이용자가 제어하는) 암호화를 통해 보호합니다."; /* Chat advantages information. Full text: Mega protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: [S]Confidentiality.[/S] Only the author and intended recipients are able to decipher and read the content. [S]Authenticity.[/S] The system ensures that the data received is from the sender displayed, and its content has not been manipulated during transit. */ "confidentialityExplanation"="[S]기밀성.[/S] 오직 작성자와 의도된 수신자만 항목을 해독하고 읽을 수 있습니다."; /* Chat advantages information. Full text: Mega protects your chat with end-to-end (user controlled) encryption providing essential safety assurances: [S]Confidentiality.[/S] Only the author and intended recipients are able to decipher and read the content. [S]Authenticity.[/S] The system ensures that the data received is from the sender displayed, and its content has not been manipulated during transit. */ @@ -1352,7 +1350,7 @@ "languageRestartNotification"="언어가 변경되었습니다. 재시작하려면 탭하세요."; /* The following strings are only for the App Store description, they're not used on the app (The only exception is the one with 'autorenewableDescription' key) On the 'generalDescriptionParagraph6' we must delete 'Desktop - https://mega.nz/' because the app can be rejected because of that. */ /* Paragraph 1 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph1"="MEGA는 모바일 기기를 위한 전용 앱과 함께 표준 웹 브라우저를 통해 사용 가능한 이용자가 통제하는 암호화된 클라우드 저장소를 제공합니다. 다른 클라우드 저장소 제공자들과 달리 당신의 데이터는 절대 우리가 아니라 당신의 클라이언트 기기를 통해 암호화되고 해독됩니다."; +"generalDescriptionParagraph1"="MEGA는 모바일 기기를 위한 전용 앱과 함께 표준 웹 브라우저를 통해 사용 가능한 이용자가 제어하는 암호화된 클라우드 저장소를 제공합니다. 다른 클라우드 저장소 제공자들과 달리 당신의 데이터는 절대 우리가 아니라 당신의 클라이언트 기기를 통해 암호화되고 해독됩니다."; /* Paragraph 2 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ "generalDescriptionParagraph2"="당신의 스마트폰이나 태블릿에서 파일을 업로드하고, 어느 기기에서나 어디서든지 당신의 파일을 검색, 보관, 다운로드, 스트림, 보기, 공유, 이름변경 또는 삭제하세요. 당신의 연락처와 폴더를 공유하고 그들의 업데이트를 실시간으로 보세요."; /* Paragraph 3 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ @@ -1555,7 +1553,7 @@ /* Confirmation message for user to confirm it will unarchive an archived chat. */ "unarchiveChatMessage"="이 보관된 대화를 삭제하시겠습니까?"; /* The title of the dialog to unarchive an archived chat. */ -"unarchiveChat"="보관된 대화 삭제"; +"unarchiveChat"="대화 보관 해제"; /* Title of button to archive chats. */ "archiveChat"="대화 보관"; /* Title of flag of archived chats. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="2단계 인증은 당신의 계정을 위한 보안의 두번째 층입니다. 당신만이 접근할 수 있는 6자리 코드가 없으면, 당신의 비밀번호를 알고 있을지라도 접근할 수 없습니다."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="이 기기에 2단계 인증 앱이 필요합니다"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="MEGA에서 2단계 인증을 활성화하려면 인증기 앱이 필요합니다. 당신의 휴대전화 또는 태블릿용 Google 인증기, Duo Mobile, Authy 또는 Microsoft 인증기 앱을 다운로드하고 설치할 수 있습니다."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="2단계 인증 활성화됨"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="다음번에 로그인할 때 당신의 인증 앱에 의해 제공된 6자리 코드를 입력하라고 요구될 것입니다."; +"twoFactorAuthenticationEnabledDescription"="다음번 로그인 때 당신의 인증기 앱에서 생성된 6자리 코드를 요구합니다."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="복구 키를 안전한 곳에 저장하세요"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,25 +1587,25 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="2단계 인증 비활성화됨"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="시드를 인증기 앱에서 스캔하거나 복사하세요. 휴대전화를 잃어버렸을 경우를 대비하여 이 시드를 안전한 곳에 백업하세요."; +"scanOrCopyTheSeed"="시드를 인증기 앱에서 스캔하거나 복사하세요. 기기를 잃어버렸을 경우를 대비하여 이 시드를 안전한 곳에 백업하세요."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="인증기 장치를 잃어버렸나요?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="라이브러리에 이미지 저장"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="라이브러리에 동영상 저장"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="사진에 이미지 저장"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="사진에 동영상 저장"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="다운로드한 이미지와 동영상은 오프라인 영역 대신 기기의 미디어 라이브러리에 저장됩니다."; /* Setting associated with the 'Camera' of the device */ "Camera"="카메라"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="라이브러리에 저장"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="사진에 저장"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="MEGA 앱에서 촬영한 이미지 또는 동영상의 사본을 기기의 미디어 라이브러리에 저장합니다."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="당신이 열쇠를 갖습니다"; /* Description shown in a page of the onboarding screens explaining the encryption paradigm */ -"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="보안은 우리가 존재하는 이유이며, 우리 뒤의 당신만이 파일에 접근할 수 있도록 잘 기름쳐진 암호화 기계와 함께 당신의 파일은 안전합니다."; +"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="보안은 우리의 존재 이유이며, 당신의 파일들은 당신만이 접근 할수 있도록 우리 서버에 마치 잘 관리된 암호화 기계 뒤에 있는 것처럼 매우 안전합니다."; /* Title shown in a page of the on boarding screens explaining that the chat is encrypted */ "Encrypted chat"="암호화된 대화"; /* Description shown in a page of the onboarding screens explaining the chat feature */ @@ -1617,25 +1617,25 @@ /* Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically */ "Your Photos in the Cloud"="클라우드 속 당신의 사진"; /* Description shown in a page of the onboarding screens explaining the camera uploads feature */ -"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="카메라 업로드는 어떠한 모바일 기기에서 필수 기능이며 우리가 당신을 위해 준비했습니다. 지금 계정을 만드세요."; +"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="카메라 업로드는 어떠한 모바일 기기에서든 필요한 기능이며 우리는 이 기능이 준비되어 있습니다. 지금 계정을 만드세요."; /* Button which triggers the initial setup */ "Setup MEGA"="MEGA 설정"; /* Detailed explanation of why the user should give some permissions to MEGA */ -"To fully take advantage of your MEGA account we need to ask you some permissions."="당신의 MEGA 계정의 완전한 이용을 위해 우리가 당신에게 몇 가지 권한을 요청합니다."; +"To fully take advantage of your MEGA account we need to ask you some permissions."="당신의 MEGA 계정의 원활한 이용을 위해, 우리는 몇 가지 권한 허용이 필요합니다."; /* Title label that explains that the user is going to be asked for the photos permission */ -"Allow Access to Photos"="사진에 대한 접근"; +"Allow Access to Photos"="사진첩에 대한 접근 허용"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="사진과 동영상을 공유하려면, MEGA가 당신의 사진에 접근할 수 있도록 허용해주세요"; +"Please give the MEGA App permission to access Photos to share photos and videos."="MEGA 앱이 사진과 동영상을 공유하기 위해 사진에 접근할 수 있는 권한을 주세요."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="마이크와 카메라 활성화"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ -"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="암호화된 음성과 영상 통화를 하려면, MEGA가 당신의 카메라와 마이크에 접근할 수 있도록 해주세요"; +"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="암호화된 음성과 영상 통화를 하려면, MEGA가 당신의 카메라와 마이크에 접근할 수 있도록 허용해주세요"; /* Title label that explains that the user is going to be asked for the notifications permission */ "Enable Notifications"="알림 활성화"; /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="당신이 메시지를 당신의 기기에서 즉시 받을 수 있도록 알림을 보내고자 합니다."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="접근 허용"; +"Allow Access"="접근 허용"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="파일 관리"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="소유하고 계신 모든 현재 버전의 파일들은 남습니다. 오직 오래된 구 버전의 파일 기록만이 제거됩니다."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="당신은 모든 파일의 버전 기록을 삭제하려고 합니다. 연락처로부터 공유받은 파일 버전은 그들에 의해 삭제되어야 합니다.\n\n현재 파일은 삭제되지 않는다는 점을 참고하세요."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="휴지통 비움 일정:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="다음 날짜보다 오래된 파일 삭제"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="휴지통은 자동으로 비워집니다. 최단 기간은 7일입니다."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="휴지통 비우기 스케줄러를 해제하거나 더 긴 보유 기간을 설정하려면, PRO 요금제를 구독하여야 합니다."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="다른 모든 세션을 닫고 싶으신가요? 이것은 현재 세션을 제외한 모든 활성 세션에서 당신을 로그아웃 시킵니다."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="다른 모든 세션을 닫고 싶으신가요? 이것은 현재 세션을 제외한 모든 활성 세션에서 당신을 로그아웃 시킵니다."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="사진에 저장"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="사진에 저장중..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="사진에 저장함"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="항목을 저장할 수 없음"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="목록 보기"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="썸네일 보기"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@님이 통화에 참여하였습니다."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@님이 통화를 떠났습니다."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="안 좋은 연결."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="활성화된 그룹 통화가 있습니다. 참여하려면 탭하세요."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="%@ 통화로 돌아가려면 터치하세요"; +/* Menu item to change from grid view to list view */ +"List view"="목록 보기"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="썸네일 보기"; +/* There are no notifications to display. */ +"No notifications"="알림 없음"; +/* The header of a notification related to payments */ +"Payment info"="결제 정보"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO 회원권이 곧 만료됩니다"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="게시 중단 알림"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="게시 중단 복원됨"; +/* Label shown inside an unseen notification */ +"New"="신규"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="연락처 요청을 발송함"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="연락처 요청을 취소하였습니다"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="알림: 연락처 요청이 있습니다"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="당신을 연락처에서 삭제하였습니다"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="양방향 연락처로 등록되었습니다"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="계정이 삭제/비활성화 되었습니다"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="연락이 차단되었습니다"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="연락처 요청을 무시하였습니다"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="연락처 요청을 수락하였습니다"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="연락처 요청을 거절하였습니다"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="연락처 요청이 수락되었습니다."; +/* When somebody denied your contact request */ +"Denied your contact request"="당신의 연락처 요청이 거절되었습니다"; +/* notification text */ +"A user has left the shared folder {0}"="이용자가 공유된 폴더 {0}을/를 떠났습니다"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="폴더에 대한 접근이 제거됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="파일 1개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="파일 %lld개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="폴더 1개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="폴더 %lld개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="파일 1개와 폴더 1개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="파일 %lld개와 폴더 1개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="파일 1개와 폴더 %lld개 추가됨"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="파일 [A]개와 폴더 [B]개 추가됨"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="[X] 항목이 공유에서 제거됨"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="%1 요금제에 대한 지불이 수신되었습니다."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="%1 요금제에 대한 지불이 처리되지 않았습니다."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="PRO 회원권이 1일 이내에 만료됩니다."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="PRO 회원권이 %1일 이내에 만료됩니다."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="공개적으로 공유된 %1 (%2)가 게시 중단되었습니다."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="게시 중단 %1 (%2)가 복원되었습니다."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="당신의 연락처가 당신이 최근에 MEGA에서 활동한 시간을 볼 수 있도록 합니다. 만약 해제하면 당신의 연락처의 활동 상황을 볼 수 없게 됩니다."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="보기"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="최근 접속 시간 %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="아주 오래 전에 마지막 접속함"; +/* */ +"Today"="오늘"; \ No newline at end of file diff --git a/iMEGA/Languages/nl.lproj/Localizable.strings b/iMEGA/Languages/nl.lproj/Localizable.strings index 0251cc1d4b..c6008435d8 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.strings +++ b/iMEGA/Languages/nl.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="U kunt geen lege map downloaden"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Uw account is reeds geactiveerd. Log a.u.b. in."; +"accountAlreadyConfirmed"="Uw account is geactiveerd. Log in."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Ongeldige koppeling"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Vul een correct email adres in"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Het e-mailadres is ongeldig"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Correct wachtwoord vereist"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Dit wachtwoord zal de meest geavanceerde brute-force aanvallen weerstaan. Zorg ervoor dat u deze goed onthoudt."; /* */ "agreeWithTheMEGATermsOfService"="Ik ga akkoord met de MEGA Algemene Voorwaarden"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Vul uw naam in"; -/* */ -"invalidFirstNameAndLastName"="Ongeldige voor- en/of achternaam."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Wachtwoorden komen niet overeen"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="In afwachting van e-mailbevestiging."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Vul aub uw wachtwoord in om uw account te bevestigen"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Bevestig uw account"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Bevestig account"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Verkeerd wachtwoord"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="Help"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Helpdesk"; +"helpCentreLabel"="Help Centrum"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Meedoen aan Beta"; /* Title to rate the app */ "rateUsLabel"="Beoordeel ons"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Upgrade later in Mijn Account"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Cloud Schijf is bijna vol.[/S] [A]Upgrade nu[/A] naar een PRO account en krijg [S]tot 4 TB (4096 GB)[/S] cloud opslag ruimte."; +"cloudDriveIsAlmostFull"="[S]Cloud Schijf is bijna vol.[/S] [A]Upgrade nu[/A] naar een PRO account en krijg [S]tot 8 TB (8192 GB)[/S] aan cloud opslag ruimte."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Uw account is vol.[/S] [A] Upgrade nu[/A] naar een PRO account en krijg [S] tot 4 TB (4096 GB)[/S] aan cloud opslag ruimte."; +"cloudDriveIsFull"="[S]Uw account is vol.[/S] [A] Upgrade nu[/A] naar een PRO account en krijg [S]tot 8 TB (8192 GB) [/S] aan cloud opslag ruimte."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Opslag"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Instellingen"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Voer de 6-cijferige code gegenereerd door uw Authenticatie applicatie in."; +"pleaseEnterTheSixDigitCode"="Voer de 6-cijferige code gegenereerd door uw Authenticatie Applicatie in."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Waarom heeft u Twee-Stap Authenticatie nodig?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Twee-Stap Authenticatie is een tweede laag aan beveiliging voor uw account. Wat betekend dat zelfs als iemand uw wachtwoord weet ze geen toegang hebben zonder het, zonder ook toegang te hebben tot de zes cijferige code waar alleen u toegang tot heeft."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="U heeft een twee-stap authenticatie applicatie nodig op dit apparaat"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="U heeft een authenticatie applicatie nodig om 2FA in te schakelen op MEGA.U kunt de applicatie Google Authenticator, Duo Mobile, Authy of Microsoft Authenticator downloaden en installeren voor uw telefoon of tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Twee-Stap Authenticatie Ingeschakeld"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Volgende keer dat u inlogt op uw account wordt u gevraagd een 6-cijferige code gegeven door uw authenticatie applicatie in te voeren."; +"twoFactorAuthenticationEnabledDescription"="De volgende keer dat u inlogt in uw account wordt u gevraagd een 6-cijferige code in te voeren die gegeven is door uw Authenticatie Applicatie."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Sla uw Herstelsleutel op op een veilige locatie"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Twee-Stap Authenticatie Uitgeschakeld"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Bent u uw Authenticatie apparaat verloren?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Afbeeldingen opslaan in Bibliotheek"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Video's opslaan in Bibliotheek"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Afbeeldingen Opslaan in Foto's"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Video's Opslaan in Foto's"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Afbeeldingen en/of video's die gedownload zijn worden opgeslagen in de media bibliotheek van het apparaat in plaats van de Offline sectie."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Opslaan in Bibliotheek"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Opslaan in Foto's"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Maak een kopie van de afbeeldingen en video's die gemaakt zijn van de MEGA applicatie in de media bibliotheek van uw apparaat."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="U heeft de sleutels"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Verleen Toegang tot Foto's"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Om foto's en video's te delen, verleen MEGA toegang tot uw foto's"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Geef de MEGA Applicatie toestemming om toegang tot foto's om foto's en video's te delen."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Microfoon en Camera inschakelen"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We willen u notificaties sturen zodat u nieuwe berichten direct op uw apparaat kunt ontvangen."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Toegang inschakelen"; +"Allow Access"="Toegang Accepteren"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Bestandsmanagement"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Alle huidige bestanden blijven behouden. Alleen oudere versies van uw bestanden worden verwijderd."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="U staat op het punt de versie geschiedenis van alle bestanden te verwijderen. Alle bestanden die gedeeld zijn van jou naar een contact moeten door hun verwijderd worden.\n\nHou er rekening mee dat de huidige versies niet verwijderd worden."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Vuilnisbak Schoonmaak Rooster:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Verwijder bestanden ouder dan"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="De Prullenbak kan automatisch voor u worden gewist. De minimale periode is 7 dagen."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Om de planner van de Prullenbak uit te schakelen of een langere bewaar periode in te stellen, is het nodig om u in te schrijven voor een PRO abonnement"; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Wilt u alle andere sessies sluiten? Dit logt u uit op alle andere actieve sessies behalve de huidige sessie."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Wilt u alle andere sessies sluiten? Dit logt u uit op alle andere actieve sessies behalve de huidige sessie."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Opslaan naar Foto's"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Opslaan naar Foto's"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Opgeslagen naar Foto's"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Kon Item niet opslaan"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Lijstweergave"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Miniatuurweergave"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ heeft zich toegevoegd tot de oproep."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ heeft de oproep verlaten."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Slechte connectie."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Er is een actieve groep oproep. Tik om mee te doen."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Tik om %@ terug te bellen"; +/* Menu item to change from grid view to list view */ +"List view"="Lijstweergave"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Miniatuurweergave"; +/* There are no notifications to display. */ +"No notifications"="Geen meldingen"; +/* The header of a notification related to payments */ +"Payment info"="Betalingsinformatie"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO abonnement verloopt binnenkort"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Bericht van takedown"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Takedown hersteld"; +/* Label shown inside an unseen notification */ +"New"="Nieuw"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Heeft u een contact verzoek verzonden"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Heeft het contactgegevens verzoek ingetrokken"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Herinnering: u hebt een contactpersoon verzoek"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Heeft u verwijderd als contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Jullie zijn nu beide contacten"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Account is verwijderd/gedeactiveerd"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Heeft u geblokkeerd als contactpersoon"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="U hebt een contactverzoek genegeerd"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="U hebt een contact verzoek geaccepteerd"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="U hebt een contact verzoek afgewezen"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Heeft uw contact verzoek aanvraag"; +/* When somebody denied your contact request */ +"Denied your contact request"="Heeft uw verzoek afgewezen"; +/* notification text */ +"A user has left the shared folder {0}"="Een gebruiker heeft de gedeelde map {0} verlaten"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Toegang tot mappen werd verwijderd."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1 bestand toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld bestanden toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1 map toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld mappen toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1 bestand en 1 map toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="%lld bestanden en 1 map toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="1 bestand en %lld mappen toegevoegd"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[A] bestanden en [B] mappen toegevoegd"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Verwijder [X] items van gedeelde map"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Uw betaling voor het %1 abonnement is ontvangen."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Uw betaling voor het %1 abonnement is mislukt."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Uw PRO abonnement verloopt binnen 1 dag."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Uw PRO abonnement zal binnen %1 dagen verlopen."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Uw publiekelijk gedeelde %1 (%2) is weggehaald."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Uw weggehaalde %1 (%2) is hersteld."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Uw contacten toestaan om te zien wanneer u laatst actief was op MEGA. Als dit uitgeschakeld staat bent u niet in staat de activiteitsstatus te zien van uw contacten."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Weergeven"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Laatst gezien %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Laatst gezien is lang geleden"; +/* */ +"Today"="Vandaag"; \ No newline at end of file diff --git a/iMEGA/Languages/pl.lproj/Localizable.strings b/iMEGA/Languages/pl.lproj/Localizable.strings index b24479fddd..a31857a934 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.strings +++ b/iMEGA/Languages/pl.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Nie można pobrać pustego katalogu"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Twoje konto jest już aktywne. Zaloguj się."; +"accountAlreadyConfirmed"="Twoje konto zostało aktywowane. Proszę się zalogować."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Nieprawidłowy link"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Wprowadź poprawny adres email"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Format adresu email jest nie prawidłowy"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Wprowadź poprawne hasło"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="To hasło będzie wytrzymałe na najbardziej wyrafinowane ataki brute-force. Upewnij się, że jesteś w stanie je zapamiętać."; /* */ "agreeWithTheMEGATermsOfService"="Akeceptuję regulamin MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Wprowadź poprawną nazwę"; -/* */ -"invalidFirstNameAndLastName"="Imię i/lub nazwisko zostało podane błędnie."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Hasła do siebie nie pasują"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Oczekiwanie na potwierdzenie adresu email."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Wprowadź hasło, w celu potwierdzenia konta"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Potwierdź swoje konto"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Potwierdź konto"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Błędne hasło"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="Centrum pomocy"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Dołącz do Bety"; /* Title to rate the app */ "rateUsLabel"="Oceń nas"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Możesz zmienić rodzaj konto w ustawieniach"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Twoje konto jest prawie pełne.[/S] [A]Zmień konto[/A] na PRO i otrzymaj [S]do 4 TB (4096 GB)[/S] miejsca dostępnego na pliki."; +"cloudDriveIsAlmostFull"="[S]Cloud Drive jest prawie pełny.[/S] [A]Zaktualizuj teraz [/A] do konta PRO i uzyskaj [S]do 8 TB (8192 GB)[/S] przestrzeni dyskowej w chmurze."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Twoje konto jest pełne.[/S] [A]Zmień konto[/A] na PRO i otrzymaj [S]do 4 TB (4096 GB)[/S] miejsca dostępnego na pliki."; +"cloudDriveIsFull"="[S]Twoje konto jest pełne.[/S] [A]Zaktualizuj teraz[/A] do konta PRO i uzyskaj[S] do 8 TB (8192 GB)[/S] miejsca w chmurze."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Pojemności"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Rozpocznij instalację"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Wprowadź 6-cyfrowy kod wygenerowany przez aplikację do uwierzytelniania."; +"pleaseEnterTheSixDigitCode"="Wprowadź 6-cyfrowy kod wygenerowany przez aplikację Authenticator."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Dlaczego potrzebujesz uwierzytelniania dwuetapowego?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Uwierzytelnianie dwuetapowe to druga warstwa zabezpieczeń Twojego konta. Co oznacza, że nawet jeśli ktoś zna twoje hasło, nie może uzyskać do niego dostępu, nie mając dostępu do sześciocyfrowego kodu, do którego masz dostęp."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Potrzebujesz aplikacji uwierzytelniania dwustopniowego na tym urządzeniu"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Potrzebujesz aplikacji uwierzytelniającej, by włączyć 2FA na MEGA. Możesz pobrać i zainstalować aplikację Google Authenticator, Duo Mobile, Authy lub Microsoft Authenticator na swój telefon lub tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Uwierzytelnianie dwuetapowe włączone"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Gdy następnym razem zalogujesz się na swoje konto, zostaniesz poproszony o podanie 6-cyfrowego kodu dostarczonego przez Twoją aplikację do uwierzytelniania."; +"twoFactorAuthenticationEnabledDescription"="Gdy następnym razem zalogujesz się na swoje konto, zostaniesz poproszony o podanie 6-cyfrowego kodu podanego przez aplikację Authenticator."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Zapisz klucz zapasowy w bezpiecznej lokalizacji"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Uwierzytelnianie dwuetapowe wyłączone"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Zeskanuj lub skopiuj kod do aplikacji do uwierzytelniania. Pamiętaj, aby wykonać kopię zapasową tego materiału w bezpieczne miejsce na wypadek zgubienia telefonu."; +"scanOrCopyTheSeed"="Zeskanuj lub skopiuj materiał siewny do aplikacji Authenticator. Pamiętaj, aby wykonać kopię zapasową tego materiału siewnego w bezpieczne miejsce na wypadek utraty urządzenia."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Zgubiłeś swoje urządzenie?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Zapisz obrazy w bibliotece"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Zapisz wideo w bibliotece"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Zapisz obrazy w zdjęciach"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Zapisz wideo w zdjęciach"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Zdjęcia i / lub pobrane filmy będą przechowywane w bibliotece multimediów urządzenia zamiast w sekcji Offline."; /* Setting associated with the 'Camera' of the device */ "Camera"="Aparat"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Zapisz w bibliotece"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Zapisz w zdjęciach"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Zapisz kopię zdjęć i filmów pobranych z aplikacji MEGA w bibliotece multimediów urządzenia."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Ty przechowujesz klucze"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Zezwalaj na dostęp do zdjęć"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Aby udostępniać zdjęcia i filmy, zezwól MEGA na dostęp do zdjęć"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Zezwól MEGA na dostęp do Zdjęć, aby udostępniać zdjęcia i filmy."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Włącz mikrofon i kamerę"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Chcemy wysyłać Ci powiadomienia, aby natychmiast otrzymywać nowe wiadomości na Twoim urządzeniu."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Włącz dostęp"; +"Allow Access"="Umożliwić dostęp"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Zarządzanie plikami"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Wszystkie bieżące pliki pozostaną. Tylko historyczne wersje twoich plików zostaną usunięte."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Zamierzasz usunąć historie wersji wszystkich plików. Każda wersja pliku udostępniona Ci z kontaktu będzie musiała zostać usunięta przez użytkownika.\n\n Należy pamiętać, że bieżące pliki nie zostaną usunięte."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Ustawienia opróżniania kosza:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Usuń pliki starsze niż"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Kosz może być opróżniany automatycznie. Minimalny okres to 8 dni."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Aby wyłączyć harmonogram czyszczenia śmieci lub ustawić dłuższy okres przechowywania, musisz subskrybować plan PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Czy chcesz zamknąć wszystkie inne sesje? Spowoduje to wylogowanie wszystkich innych aktywnych sesji oprócz bieżącego."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Czy chcesz zamknąć wszystkie inne sesje? Spowoduje to wylogowanie wszystkich innych aktywnych sesji oprócz bieżącego."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Zapisz w Zdjęciach"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Zapisywanie do zdjęć..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Zapisano w Zdjęciach"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Nie można zapisać elementu"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Widok listy"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Podgląd zdjęcia"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ dołączył do połączenia."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ opuścił połączenie."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Słabe połączenie."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Istnieje aktywne połączenie grupowe. Kliknij, aby dołączyć."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Dotknij, aby wrócić do połączenia %@"; +/* Menu item to change from grid view to list view */ +"List view"="Widok listy"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Podgląd zdjęcia"; +/* There are no notifications to display. */ +"No notifications"="Brak powiadomień"; +/* The header of a notification related to payments */ +"Payment info"="Informacje o płatności"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Plan PRO niebawem wygaśnie"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Prośba o zablokowanie treści"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Informacja o przywróceniu plików"; +/* Label shown inside an unseen notification */ +"New"="Nowe"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Wysłane zaproszenia"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Anulowali swoje zaproszenia"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Przypomnienie: masz nowe zaproszenie"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Usunął cię jako kontakt"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Jesteście teraz znajomymi"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Konto zostało usunięte/zablokowane"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Zablokował Cię jako kontakt"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Zignorowałeś nadchodzące połączone"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Zaakceptowałeś nowy kontakt"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Odmówiłeś akceptacji dla kontaktu"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Zaakceptowane zgłoszenia"; +/* When somebody denied your contact request */ +"Denied your contact request"="Anulowane zaproszenia"; +/* notification text */ +"A user has left the shared folder {0}"="Użytkownik opuścił udostępniony folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Dostęp do katalogów został usunięty"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Dodano 1 plik"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Dodano %lld plików"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Dodano 1 katalog"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Dodano %lld katalogów"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Dodano 1 plik i 1 katalog"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Dodano %lld plików i 1 katalog"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Dodano 1 plik i %lld katalogów"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Dodano [A] plików i [B] katalogów"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Usunięto [X] elementów z udostępnionego katalogu"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Twoja płatność za konto %1 została potwierdzona."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Twoja płatność za konto %1 nie powiodła się."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Plan PRO wygaśnie za 1 dzień."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Plan PRO wygaśnie za %1 dni."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Twój udostępniony publicznie katalog %1 (%2) został zablokowany."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Zablokowane pliki %1 (%2) zostały przywrócone."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Pozwól swoim kontaktom zobaczyć ostatni raz, kiedy byłeś aktywny w MEGA. W przypadku wyłączenia nie będzie można zobaczyć stanu aktywności kontaktów."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Pokaż"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Mniej niż %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Ostatnio widziany dawno temu"; +/* */ +"Today"="Dzisiaj"; \ No newline at end of file diff --git a/iMEGA/Languages/pt-br.lproj/InfoPlist.strings b/iMEGA/Languages/pt-br.lproj/InfoPlist.strings index b8fda35617..8e50fe6348 100644 --- a/iMEGA/Languages/pt-br.lproj/InfoPlist.strings +++ b/iMEGA/Languages/pt-br.lproj/InfoPlist.strings @@ -1,13 +1,13 @@ /* Microphone usage description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ -"NSMicrophoneUsageDescription"="O MEGA acessa o microfone quando você gravar um vídeo ou fizer uma chamada usando o aplicativo."; +"NSMicrophoneUsageDescription"="O MEGA acessa o microfone quando você grava um vídeo ou faz uma chamada usando o aplicativo."; /* Camera usage description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ -"NSCameraUsageDescription"="O MEGA acessa a sua câmera quando você gravar um vídeo, tirar uma foto ou fizer uma chamada usando o aplicativo."; +"NSCameraUsageDescription"="O MEGA acessa a sua câmera quando você grava um vídeo, tira uma foto ou faz uma chamada usando o aplicativo."; /* Photo library usage description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ "NSPhotoLibraryUsageDescription"="O MEGA acessa as suas fotos e/ou vídeos quando você seleciona alguns deles para fazer upload, ou quando você ativar os Uploads da câmera."; /* Contacts Usage Description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ "NSContactsUsageDescription"="O MEGA acessa os seus contatos quando você os seleciona para salvar ou exportar."; /* Write-only access to the user’s photo library description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ -"NSPhotoLibraryAddUsageDescription"="O MEGA requer acesso à sua biblioteca de fotos para adicionar fotos e vídeos à sua galeria do seu dispositivo"; +"NSPhotoLibraryAddUsageDescription"="O MEGA necessita acessar a sua biblioteca multimídia para adicionar fotos e vídeos à galeria do seu dispositivo"; /* Face ID usage description. To protect user privacy Apple requires a purpose string explaining why will be accessed. */ "NSFaceIDUsageDescription"="O MEGA acessa o Face ID para permitir desbloquear facilmente o passcode do aplicativo caso você ative esta opção."; /* Label for any 'Search' button, link, etc. - (String as short as possible). */ diff --git a/iMEGA/Languages/pt-br.lproj/Localizable.strings b/iMEGA/Languages/pt-br.lproj/Localizable.strings index 43922c73e8..108d9529c7 100644 --- a/iMEGA/Languages/pt-br.lproj/Localizable.strings +++ b/iMEGA/Languages/pt-br.lproj/Localizable.strings @@ -31,13 +31,13 @@ /* Alert message to notify that MEGA video calls needs the Camera permission and the call will end*/ "cameraPermissionsInCall"="Acesse as Configurações do seu dispositivo para permitir que o app MEGA acesse a sua câmera. Ao fazer isso, a chamada terminará, porque o aplicativo se reiniciará."; /* Alert message to remember that MEGA app needs permission to use the Microphone to make calls and record videos and it doesn't have it */ -"microphonePermissions"="Acesse as Configurações do seu dispositivo para permitir que o app MEGA acesse o seu microfone."; +"microphonePermissions"="Ajuste as Configurações do seu dispositivo para permitir que o MEGA acesse o seu microfone."; /* Alert message to explain that the MEGA app needs permission to access your device photos */ "photoLibraryPermissions"="Você deve dar permissão ao MEGA para acessar às suas fotos nos Ajustes"; /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Não é possível baixar uma pasta vazia"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="A sua conta já foi ativada. Por favor, faça o login."; +"accountAlreadyConfirmed"="A sua conta está ativa. Por favor, faça o login."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Link inválido"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -63,7 +63,7 @@ /* Message shown when the app is waiting for the server to complete a request due to a HTTP error 500. */ "serversAreTooBusy"="Servidores ocupados. Por favor, aguarde."; /* Message shown when the app is waiting for the server to complete a request due to connectivity issue. */ -"unableToReachMega"="Não é possível acessar o MEGA. Por favor, verifique a sua conexão ou tente novamente mais tarde."; +"unableToReachMega"="Não foi possível acessar o MEGA. Por favor, verifique a sua conexão ou tente novamente mais tarde."; /* Message shown when the app is waiting for the server to complete a request due to a rate limit (error -4). */ "tooManyRequest"="Muitas solicitações. Por favor, aguarde."; /* Alert title shown when you need to log in to continue with the action you want to do */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Digite um email válido"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="O formato do email é inválido"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Digite uma senha válida"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,22 +130,20 @@ "passwordStrong"="Esta senha vai resistir aos ataques de força-bruta mais sofisticados. Por favor, certifique-se de que você vai lembrar dela."; /* */ "agreeWithTheMEGATermsOfService"="Eu concordo com os Termos de serviço do MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Digite um nome válido"; -/* */ -"invalidFirstNameAndLastName"="Nome e/ou sobrenome inválido."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="As senhas não coincidem"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ "termsCheckboxUnselected"="Você precisa aceitar os termos de serviço para criar uma conta no MEGA."; /* Error text shown when the users tries to create an account with an email already in use */ -"emailAlreadyRegistered"="Este endereço de email já está cadastrado no MEGA."; +"emailAlreadyRegistered"="Já existe uma conta no MEGA com esse email."; /* Title shown just after doing some action that requires confirming the action by an email */ "awaitingEmailConfirmation"="Aguardando email de confirmação."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Por favor, digite sua senha para confirmar sua conta"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirme a sua conta"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirmar conta"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Senha incorreta"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -267,7 +265,7 @@ /* Label for the state of a transfer when is being completing - (String as short as possible). */ "Completing..."="Completando..."; /* Label for the state of a transfer when is being retrying - (String as short as possible). */ -"Retrying..."="Tentando de novo..."; +"Retrying..."="Tentando novamente..."; /* Section title of the 'Sort by' */ "sortTitle"="Ordenar por"; /* Button title to 'Save' the selected option */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="Central de ajuda"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Una-se a Beta"; /* Title to rate the app */ "rateUsLabel"="Nos avalie"; /* Title of the Transfers section */ @@ -906,7 +904,7 @@ /* A message which is shown once someone has invited a friend as part of the achievements program. */ "howItWorksTertiary"="Você não receberá bônus por ter convidado alguém que já tiver usado o MEGA anteriormente, e você não será notificado sobre isso."; /* Header of block with achievements bonuses. */ -"unlockedBonuses"="Bônus obtidos:"; +"unlockedBonuses"="Bônus desbloqueados:"; /* A header/title of a section which contains information about used/available storage space on a user's cloud drive. */ "storageQuota"="Cota de armazenamento"; /* The header/title of a block/section which contains information about the user's used/available transfer allowance for their account. */ @@ -982,11 +980,11 @@ /* */ "chooseYourAccountType"="Escolha o seu tipo de conta"; /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ -"youCanUpgradeLaterInMyAccount"="Você pode atualizar mais tarde em Minha conta"; +"youCanUpgradeLaterInMyAccount"="Você pode fazer upgrade mais tarde em Minha conta"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="A sua [S]Nuvem de arquivos está quase cheia.[/S] [A]Faça o upgrade[/A] para uma conta PRO e obtenha [S]até 4TB (4096 GB)[/S] de espaço de armazenamento em nuvem."; +"cloudDriveIsAlmostFull"="A sua [S]Nuvem de arquivos está quase cheia.[/S] [A]Faça upgrade[/A] para uma conta PRO e obtenha [S]até 8 TB (8192 GB)[/S] de espaço de armazenamento em nuvem."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]A sua conta está cheia.[/S] [A]Faça o upgrade[/A] para uma conta PRO e obtenha [S]até 4TB (4096 GB[/S] de espaço de armazenamento em nuvem."; +"cloudDriveIsFull"="[S]A sua conta está cheia.[/S] [A]Faça upgrade[/A] para uma conta PRO e obtenha [S]até 8 TB (8192 GB)[/S] de espaço de armazenamento em nuvem."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Armazenamento"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1050,7 +1048,7 @@ /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="Ativar o modo de depuração"; /* Alert message shown when the DEBUG mode is enabled */ -"enableDebugMode_message"="Um log será criado na seção offline (MEGAiOS.log). Os logs podem conter informações relacionadas à sua conta."; +"enableDebugMode_message"="Um log será criado na seção Offline (MEGAiOS.log). Os logs podem conter informações relacionadas à sua conta."; /* Alert title shown when the DEBUG mode is disabled */ "disableDebugMode_title"="Desativar o modo de depuração"; /* Alert message shown when the DEBUG mode is disabled */ @@ -1100,7 +1098,7 @@ /* Title used in settings that enables the generation of link previews in the chat */ "richUrlPreviews"="Visualização de URLs avançadas"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ -"richPreviewsFooter"="Aprimore a sua experiência no MEGAchat. O conteúdo de URLs será recuperado sem criptografia de ponta a ponta."; +"richPreviewsFooter"="Melhore a sua experiência no MEGAchat. O conteúdo de URLs será recuperado sem criptografia de ponta a ponta."; /* Section title of a button where you can enable mobile data for voice and video calls. */ "voiceAndVideoCalls"="Chamadas de voz e vídeo"; /* Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature. */ @@ -1112,13 +1110,13 @@ /* */ "1Minute"="1 minuto"; /* Footer text to explain the meaning of the functionaly 'Auto-away' of your chat status. */ -"showMeAwayAfterXMinutesOfInactivity"="Aparecer como ausente depois de [X] minutos de inatividade"; +"showMeAwayAfterXMinutesOfInactivity"="Mudar o meu status para ausente ao estar inativo por [X] minutos"; /* Footer text to explain the meaning of the functionaly 'Auto-away' of your chat status. */ -"showMeAwayAfter1MinuteOfInactivity"="Mostre-me como ausente depois de 1 minuto de inatividade"; +"showMeAwayAfter1MinuteOfInactivity"="Mudar o meu status para ausente ao estar inativo por 1 minuto"; /* Label title to enable/disable the feature of the chat status that mantains your chosen status even if you don't have connected devices */ "statusPersistence"="Continuidade de status"; /* Footer text to explain the meaning of the functionaly 'Status Persistence' of your chat status. */ -"maintainMyChosenStatusAppearance"="Manter o status escolhido mesmo quando eu não tiver nenhum dispositivo conectado."; +"maintainMyChosenStatusAppearance"="Manter o status escolhido mesmo quando eu não estiver conectado em nenhum dispositivo."; /* A log message in a chat to indicate that the message has been edited by the user. */ "edited"="(editado)"; /* */ @@ -1232,7 +1230,7 @@ /* Button label for sending file attachments through the chat to another user. */ "sendFiles"="Enviar arquivos"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ -"enableRichUrlPreviews"="Ativar visualização de URLs avançadas"; +"enableRichUrlPreviews"="Ativar a visualização de URLs avançadas"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ "alwaysAllow"="Permitir sempre"; /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ @@ -1240,13 +1238,13 @@ /* */ "never"="Nunca"; /* After several times (right now set to 3) that the user may had decided to click \"Not now\" (for when being asked if he/she wants a URL preview to be generated for a link, posted in a chat room), we change the \"Not now\" button to \"Never\". If the user clicks it, we ask for one final time - to ensure he wants to not be asked for this anymore and tell him that he can do that in Settings. */ -"richPreviewsConfirmation"="Você vai desativar permanentemente a visualização de URL avançada. Você pode reativá-la nas suas configurações. Você quer prosseguir?"; +"richPreviewsConfirmation"="Você vai desativar permanentemente a visualização de URLs avançadas, mas poderá reativá-la nas suas configurações. Você quer continuar?"; /* Once a preview is generated for a message which contains URLs, the user can remove it. Same button is also shown during loading of the preview - and would cancel the loading (text of the button is the same in both cases). */ "removePreview"="Remover visualização"; /* */ "warning"="Aviso"; /* Alert message shown when the user perform logout and has files in the Offline directory */ -"allFilesSavedForOfflineWillBeDeletedFromYourDevice"="Todos os arquivos salvos para uso offline serão excluídos do seu dispositivo. Deseja continuar?"; +"allFilesSavedForOfflineWillBeDeletedFromYourDevice"="Todos os arquivos salvos para uso offline serão excluídos do seu dispositivo. Você quer continuar?"; /* Title shown when you almost had used your available transfer quota. */ "depletedTransferQuota_title"="Cota de transferência esgotada"; /* Description shown when you almost had used your available transfer quota. */ @@ -1347,20 +1345,20 @@ /* Label to show that an error related with an unknown error occurs during a SDK operation. */ "Unknown error"="Erro desconhecido"; /* Text shown in an alert when the user is about to change the language of the app */ -"languageRestartAlert"="Você precisará reiniciar o MEGA depois de mudar o idioma."; +"languageRestartAlert"="Você precisará reiniciar o MEGA depois de alterar o idioma."; /* Text shown in a notification to make it easy for the user to restart the app after the language is changed */ -"languageRestartNotification"="Idioma modificado. Toque para reiniciar."; +"languageRestartNotification"="Idioma alterado. Toque para reiniciar."; /* The following strings are only for the App Store description, they're not used on the app (The only exception is the one with 'autorenewableDescription' key) On the 'generalDescriptionParagraph6' we must delete 'Desktop - https://mega.nz/' because the app can be rejected because of that. */ /* Paragraph 1 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ "generalDescriptionParagraph1"="O MEGA oferece armazenamento em nuvem criptografado controlado pelo usuário por meio de navegadores padrão, assim como aplicativos para dispositivos móveis. Diferentemente de outros provedores de armazenamento em nuvem, no MEGA os seus dados são criptografados e descriptografados apenas pelos seus dispositivos, e nunca por nós."; /* Paragraph 2 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ "generalDescriptionParagraph2"="Faça o upload dos seus arquivos a partir do seu smartphone ou tablet e, em seguida, procure, armazene, faça download, transmita, visualize, compartilhe, renomeie ou elimine os seus arquivos a qualquer momento, a partir de qualquer dispositivo, em qualquer lugar. Compartilhe pastas com os seus contatos e veja as atualizações deles em tempo real."; /* Paragraph 3 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph3"="O processo de criptografia significa que não podemos acessar ou redefinir sua senha você DEVE se lembrar (a menos que você tenha a sua chave de recuperação backup) ou você perderá o acesso aos seus arquivos armazenados."; +"generalDescriptionParagraph3"="O processo de criptografia significa que não podemos acessar ou redefinir a sua senha: é IMPRESCINDÍVEL que você se lembre dela (a menos que você tenha a sua chave de recuperação salva), ou você perderá o acesso aos seus arquivos armazenados."; /* Paragraph between 3rd and 4th of the general description of MEGA shown on the iOS App Store, part of Android string 17769 */ "generalDescriptionVideoChat"="O chat de vídeo criptografado de ponta a ponta pelo usuário garante uma privacidade total, e está disponível no navegador desde 2016. Atualmente foi ampliado para o nosso aplicativo móvel, e o histórico do chat é acessível em vários dispositivos. Os usuários também podem facilmente adicionar arquivos da sua Nuvem de arquivos no MEGA em um chat."; /* Paragraph 4 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph4"="O MEGA oferece um generoso armazenamento gratuito de 50 GB para todos os usuários registrados com bônus de conquistas, e oferece planos pagos com limites muito mais altos:"; +"generalDescriptionParagraph4"="O MEGA oferece um generoso armazenamento gratuito de 50 GB para todos os usuários cadastrados com bônus de conquistas, e oferece planos pagos com limites muito mais altos:"; /* Paragraph 5 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. Please keep the multiple line format. */ "generalDescriptionParagraph5"="Assinatura PRO LITE: 4,99€ (US$4,99) por mês ou 49,99€ (US$49,99) por ano - oferece 200 GB de espaço de armazenamento e 1 TB de cota de transferência por mês. \nAssinatura PRO I: 9,99€ (US$9,99) por mês ou 99,99€ (US$99,99) por ano - oferece 1 TB de espaço de armazenamento e 2 TB de cota de transferência por mês. \nAssinatura PRO II: 19,99€ (US$19,99) por mês ou 199,99€ (US$199,99) por ano - oferece 4 TB de espaço de armazenamento e 8 TB de cota de transferência por mês. \nAssinatura PRO III: 29,99 (US$29,99) por mês ou 299,99€ (US$299,99) por ano - oferece 8 TB de espaço de armazenamento e 16 TB de cota de transferência por mês."; /* Between paragraph 5 and 6 we have to put the string with the 'autorenewableDescription' key */ @@ -1385,7 +1383,7 @@ /* Notification text body shown when you have missed several audio calls and video calls. [A] = {number of missed audio calls}. [B] = {number of missed video calls} */ "missedAudioCallsAndMissedVideoCalls"="[A] chamadas de áudio e [B] chamadas de vídeo perdidas"; /* Button title to see the available bonus */ -"getBonus"="Consiga bônus"; +"getBonus"="Ganhe bônus"; /* Text shown under the title 'Video quality' that explains what it means */ "qualityOfVideosUploadedToAChat"="Qualidade dos vídeos enviados para um chat"; /* Label used near to the option selected to encode the videos uploaded to a chat (Low, Medium, Original) */ @@ -1481,7 +1479,7 @@ /* Label for recovery key button */ "backupRecoveryKey"="Salvar chave de recuperação"; /* Used as a message in the 'Password reminder' dialog as a tip on why confirming the password and/or exporting the recovery key is important and vital for the user to not lose any data. */ -"testPasswordText"="Por favor, verifique a sua senha abaixo, para garantir que você se lembra dela. Se você perder a sua senha, perderá o acesso aos seus dados no MEGA. [A]Mais informações[/A]"; +"testPasswordText"="Por favor, confirme a sua senha abaixo, para garantir que você se lembra dela. Se você perder a sua senha, perderá o acesso aos seus dados no MEGA. [A]Mais informações[/A]"; /* Title text for the account confirmation. */ "confirm"="Confirmar"; /* Used as a message in the "Password reminder" dialog that is shown when the user enters his password, clicks confirm and his password is correct. */ @@ -1513,9 +1511,9 @@ /* Title of the notification for a missed call */ "missedCall"="Chamada perdida"; /* When an outgoing call of user A with user B had been rejected by user B */ -"callWasRejected"="A chamada foi rejeitada"; +"callWasRejected"="Chamada recusada"; /* When an active call of user A with user B had cancelled */ -"callWasCancelled"="A chamada foi cancelada"; +"callWasCancelled"="Chamada cancelada"; /* When an active call of user A with user B had not answered */ "callWasNotAnswered"="Chamada não atendida"; /* When an active call of user A with user B had failed */ @@ -1533,13 +1531,13 @@ /* */ "xHours"="%d horas"; /* */ -"1Hour1Minute"="1 hora, 1 minuto"; +"1Hour1Minute"="1 hora e 1 minuto"; /* */ -"1HourxMinutes"="1 hora, %d minutos"; +"1HourxMinutes"="1 hora e %d minutos"; /* */ -"xHours1Minute"="%d horas, 1 minuto"; +"xHours1Minute"="%d horas e 1 minuto"; /* */ -"xHoursxMinutes"="%1$d horas, %2$d minutos"; +"xHoursxMinutes"="%1$d horas e %2$d minutos"; /* Alert title shown when users try to stream an unsupported audio/video file */ "fileNotSupported"="Arquivo não suportado"; /* Alert message shown when users try to stream an unsupported audio/video file */ @@ -1573,11 +1571,13 @@ /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ "whatIsTwoFactorAuthentication"="A autenticação de dois fatores é uma segunda camada de segurança para a sua conta."; /* Description text of the dialog displayed to start setup the Two-Factor Authentication */ -"whyYouDoNeedTwoFactorAuthenticationDescription"="A autenticação de dois fatores é uma segunda camada de segurança para a sua conta. Isso significa que, mesmo que alguém saiba a sua senha, não poderá acessar a sua conta sem ter acesso ao código de seis dígitos que só você sabe."; +"whyYouDoNeedTwoFactorAuthenticationDescription"="A autenticação de dois fatores é uma segunda camada de segurança para a sua conta. Isso significa que, mesmo que alguém saiba a sua senha, não poderá acessar a sua conta sem ter acesso ao código de seis dígitos que só você pode obter."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Você precisa de um aplicativo de autenticação de dois fatores neste dispositivo"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Você precisa de um aplicativo de autenticação para ativar a 2FA no MEGA. Você pode baixar e instalar os aplicativos Google Authenticator, Duo Mobile, Authy ou Microsoft Authenticator no seu celular ou tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ -"twoFactorAuthenticationEnabled"="Autenticação de dois fatores ativa"; +"twoFactorAuthenticationEnabled"="Autenticação de dois fatores ativada"; /* A message on the dialog shown after 2FA was successfully enabled. */ "twoFactorAuthenticationEnabledDescription"="Na próxima vez que você fizer login na sua conta, você deverá inserir um código de seis dígitos proporcionado pelo seu aplicativo de autenticação."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ @@ -1590,17 +1590,17 @@ "scanOrCopyTheSeed"="Escaneie ou copie a chave para o seu aplicativo de autenticação. Certifique-se de salvar esta chave em um lugar seguro, caso você perca o seu dispositivo."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Você perdeu o seu dispositivo de autenticação?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Salvar imagens no Fotos"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Salvar vídeos no Fotos"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Salvar imagens no Fotos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Salvar vídeos no Fotos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Imagens e/ou vídeos baixados serão armazenados na biblioteca multimídia do seu dispositivo, em vez da seção Offline."; /* Setting associated with the 'Camera' of the device */ "Camera"="Câmera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Salvar no Fotos"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Salvar no Fotos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Salve uma cópia das fotos tiradas e dos vídeos gravados com o aplicativo do MEGA na biblioteca multimídia do seu dispositivo."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Você tem a chave"; @@ -1615,17 +1615,17 @@ /* Description shown in a page of the onboarding screens explaining contacts */ "Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"="Adicione contatos, crie uma rede, colabore, faça chamadas de voz e vídeo sem sair do MEGA"; /* Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically */ -"Your Photos in the Cloud"="As suas fotos na nuvem"; +"Your Photos in the Cloud"="As suas fotos na Nuvem"; /* Description shown in a page of the onboarding screens explaining the camera uploads feature */ -"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Sabemos que os envios da câmera são um recurso essencial para qualquer dispositivo móvel. Crie a sua conta agora."; +"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Sabemos que os Uploads da câmera são um recurso essencial para qualquer dispositivo móvel. Crie a sua conta agora."; /* Button which triggers the initial setup */ "Setup MEGA"="Configurar o MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para aproveitar ao máximo a sua conta no MEGA, precisamos de algumas permissões."; /* Title label that explains that the user is going to be asked for the photos permission */ -"Allow Access to Photos"="Permitir acesso a fotos"; +"Allow Access to Photos"="Permitir acesso ao Fotos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Para compartilhar fotos e vídeos, permita que o MEGA acesse as suas fotos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Permita ao aplicativo do MEGA acessar o Fotos para poder compartilhar fotos e vídeos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Ativar microfone e câmera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Gostaríamos de enviar notificações para você receber imediatamente as novas mensagens no seu dispositivo."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Permitir acesso"; +"Allow Access"="Permitir acesso"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Gerenciamento de arquivos"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1643,7 +1643,7 @@ /* Subtitle of the option to enable or disable file versioning on Settings section */ "Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Ative ou desative o controle de versões de arquivos para toda a sua conta. \nVocê ainda pode receber versões de arquivos em pastas compartilhadas se os seus contatos tiverem essa função ativa."; /* A confirmation message when the user chooses to disable file versioning. */ -"When file versioning is disabled, the current version will be replaced with the new version once a file is updated (and your changes to the file will no longer be recorded). Are you sure you want to disable file versioning?"="Se o controle de versões de arquivos estiver desativado, assim que um arquivo for atualizado a versão atual será substituída pela nova versão assim (e suas alterações no arquivo não serão mais gravadas). Você tem certeza de que deseja desativar o controle de versões de arquivos?"; +"When file versioning is disabled, the current version will be replaced with the new version once a file is updated (and your changes to the file will no longer be recorded). Are you sure you want to disable file versioning?"="Se o controle de versões de arquivos estiver desativado, ao modificar um arquivo a versão atual será substituída pela nova versão (e suas alterações no arquivo já não serão salvas). Você tem certeza de que quer desativar o controle de versões de arquivos?"; /* Settings preference title to show file versions info of the account */ "File versions"="Versões de arquivos"; /* A title message in the user’s account settings for showing the storage used for file versions. */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Todos os arquivos atuais permanecerão. Apenas as versões antigas dos seus arquivos serão deletadas."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Você está prestes a excluir o histórico de versões de todos os arquivos. Versões de arquivos compartilhados com você por um contato deve ser excluídas por ele.\n\nTenha em conta que os arquivos atuais não serão excluídos."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Planejador de limpeza da lixeira:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Remover arquivos com mais de"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Podemos limpar a sua lixeira automaticamente. O período mínimo é de 7 dias."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Para desativar o Planejador de limpeza da lixeira ou definir um período de retenção mais longo, você precisa assinar um plano PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Você quer fechar todas as outras sessões? Essa ação fará logout de todas as outras sessões ativas, exceto a atual."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Você quer fechar as demais sessões? Essa ação faráo logout de todas as outras sessões ativas, exceto a atual."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Salvar no Fotos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Salvando no Fotos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Salvo no Fotos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Não foi possível salvar"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Visualização em lista"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Visualização em miniaturas"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ entrou na chamada."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ saiu da chamada."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Conexão ruim."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Há uma chamada em grupo ativa. Toque para participar."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Toque para voltar à chamada %@"; +/* Menu item to change from grid view to list view */ +"List view"="Visualização em lista"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Visualização em miniaturas"; +/* There are no notifications to display. */ +"No notifications"="Não há notificações"; +/* The header of a notification related to payments */ +"Payment info"="Informações de pagamento"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="O seu plano PRO expirará em breve"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Aviso de remoção"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Remoção revogada"; +/* Label shown inside an unseen notification */ +"New"="Nova"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Enviou uma solicitação de contato"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Cancelou a solicitação de contato"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Lembrete: você possui uma solicitação de contato"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Eliminou você como contato"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Vocês agora são contatos"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Esta conta foi deletada ou desativada"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Bloqueou você como contato"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Você ignorou uma solicitação de contato"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Você aceitou uma solicitação de contato"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Você recusou uma solicitação de contato"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Aceitou a sua solicitação de contato"; +/* When somebody denied your contact request */ +"Denied your contact request"="Recusou a sua solicitação de contato"; +/* notification text */ +"A user has left the shared folder {0}"="Um usuário deixou a pasta compartilhada {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="O acesso às pastas foi removido."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1 arquivo adicionado"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld arquivos adicionados"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1 pasta adicionada"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld pastas adicionadas"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1 pasta e 1 arquivo adicionados"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="1 pasta e %lld arquivos adicionados"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="%lld pastas e 1 arquivo adicionados"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[B] pastas e [A] arquivos adicionados"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="[X] itens foram eliminados da pasta compartilhada"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="O seu pagamento para o plano %1 foi recebido."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="O seu pagamento para o plano %1 não foi concluído."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="O seu plano PRO expirará em 1 dia."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="O seu plano PRO expirará em %1 dias."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="O(a) %1 (%2) compartilhado publicamente foi removido(a)."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="A remoção de %1 (%2) foi revogada."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Permitir que os meus contatos vejam a hora do meu último acesso ao MEGA. Se estiver desativado, você não poderá ver o último acesso dos seus contatos."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Mostrar"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Último acesso %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Último acesso há muito tempo atrás"; +/* */ +"Today"="Hoje"; \ No newline at end of file diff --git a/iMEGA/Languages/ro.lproj/Localizable.strings b/iMEGA/Languages/ro.lproj/Localizable.strings index ebf2eae484..2ef72a6e0f 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.strings +++ b/iMEGA/Languages/ro.lproj/Localizable.strings @@ -102,7 +102,7 @@ "emailInvalidFormat"="Introdu un e-mail valid"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Formatul adresei de e-mail este nevalid"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Introdu o parolă validă"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="E-mail"; @@ -130,10 +130,8 @@ "passwordStrong"="Această parolă va rezista în faţa celor mai sofisticate atacuri de tip „brute-force”. Te rugăm să te asiguri că o vei reţine."; /* */ "agreeWithTheMEGATermsOfService"="Sunt de acord cu Termenii de utilizare a serviciului MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Introdu un nume valid"; -/* */ -"invalidFirstNameAndLastName"="Prenume şi/sau nume nevalide."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Parolele nu se potrivesc"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Se aşteaptă confirmarea e-mailului."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Te rugăm să introduci parola pentru a confirma contul"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Confirmă contul"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Confirmă contul"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Parolă greşită"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Call failed"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Call ended"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="De ce ai nevoie de autentificarea cu doi factori?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Managementul fişierelor"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Acest lucru va şterge definitiv toate versiunile fişierelor proprii. Orice versiune de fişier partajată cu tine de la un contact va trebui să fie ştearsă de către aceştia."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Planificatorul de golire a coşului de gunoi:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Elimină fişierele mai vechi de"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Vizualizare tip listă"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Vizualizare tip grilă"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="Vizualizare tip listă"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Vizualizare tip grilă"; +/* There are no notifications to display. */ +"No notifications"="Nicio notificare"; +/* The header of a notification related to payments */ +"Payment info"="Informaţii de plată"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Planul de membru PRO expiră în curând"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Notificare de eliminare"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Eliminare restabilită"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Ţi-a trimis o cerere de contact"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="A anulat cererea sa de contact"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Memento: Ai o cerere de contact"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="te-a şters ca contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Relaţie de contact stabilită"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Contul a fost şters/dezactivat"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Te-a blocat ca contact"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Ai ignorat o cerere de contact"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Ai acceptat o cerere de contact"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Ai refuzat o cerere de contact"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Ţi-a acceptat cererea de contact"; +/* When somebody denied your contact request */ +"Denied your contact request"="Ţi-a refuzat cererea de contact"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Accesul la foldere a fost eliminat."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="[X] elemente eliminate dintr-o partajare"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Plata pentru planul %1 a fost primită."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Plata pentru planul %1 nu a avut succes."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Planul tău de membru PRO va expira în termen de 1 zi."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Planul tău de membru PRO va expira în %1 zile."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Partajarea ta publică %1 (%2) a fost eliminată."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Eliminarea ta %1 (%2) a fost restabilită."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Astăzi"; \ No newline at end of file diff --git a/iMEGA/Languages/ru.lproj/Localizable.strings b/iMEGA/Languages/ru.lproj/Localizable.strings index e75d5ac631..1c97238372 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.strings +++ b/iMEGA/Languages/ru.lproj/Localizable.strings @@ -102,7 +102,7 @@ "emailInvalidFormat"="Введите правильный email"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Похоже, что email некорректен"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Введите правильный пароль"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Этот пароль не поддастся самым самым мощным атакам с применением полного перебора. Убедитесь, что запомнили его."; /* */ "agreeWithTheMEGATermsOfService"="Я согласен с Условиями предоставления услуг MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Введите правильное имя"; -/* */ -"invalidFirstNameAndLastName"="Неверное имя и/или фамилия."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Пароли не совпадают"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Ожидание подтверждения по электронной почте."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Пожалуйста, введите пароль, чтобы подтвердить свою учётную запись"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Подтверждение учётной записи"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Подтвердить уч. запись"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Неверный пароль"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -848,7 +846,7 @@ /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="Центр помощи"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Присоединиться к бете"; /* Title to rate the app */ "rateUsLabel"="Оценить нас"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Учётную запись можно улучшить позже в разделе «Моя учётная запись»"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Облачный диск почти заполнен.[/S] [A]Улучшите его сейчас[/A] до PRO и получите [S]до 4 ТБ (4096 ГБ)[/S] места в облаке."; +"cloudDriveIsAlmostFull"="[S]Облачный диск почти заполнен.[/S] [A]Улучшите аккаунт сейчас[/A] до PRO и получите [S]до 8 ТБ (8192 ГБ)[/S] места в облаке."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Ваш аккаунт переполнен.[/S] [A]Улучшите учётную запись[/A] и получите [S]до 4 ТБ (4096 ГБ)[/S] свободного места в облаке."; +"cloudDriveIsFull"="[S]Ваш аккаунт заполнен.[/S] [A]Улучшите его сейчас[/A] до PRO и получите [S]до 8 ТБ (8192 ГБ)[/S] свободного места в облаке."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="свободного места"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Двухфакторная аутентификация — второй слой безопасности вашего аккаунта. Это значит, что даже если кто-то знает ваш пароль, он не сможет получить к нему доступ без шестизначного кода, который есть только у вас."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Нужно приложение для двухфакторной аутентификации"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="Вам нужно приложение-аутентификатор, чтобы включить 2FA в MEGA. Вы можете загрузить и установить приложение Google Authenticator, Duo Mobile, Authy или Microsoft Authenticator на свой телефон или планшет."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Двухфакторная аутентификация включена."; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="При следующем входе в аккаунт система запросит 6-значный код из приложения аутентификации."; +"twoFactorAuthenticationEnabledDescription"="При следующем входе в аккаунт система запросит 6-значный код из приложения-аутентификатора."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Пожалуйста, сохраните ключ восстановления в безопасном месте"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Двухфакторная аутентификация отключена"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Сканируйте или скопируйте seed-ключ в приложение-аутентификатор. Сохраните seed-ключ в безопасном месте на случай потери телефона."; +"scanOrCopyTheSeed"="Сканируйте или скопируйте seed-ключ в приложение-аутентификатор. Сохраните seed-ключ в безопасном месте на случай потери устройства."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Потеряли аутентификационное устройство?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Сохранять изображения в библиотеке"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Сохранять видео в библиотеке"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Сохранять изображения в «Фотографиях»"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Сохранять видео в «Фотографиях»"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Загруженные изображения и/или видео будут сохранены в медиабиблиотеке устройства, а не в локальном разделе."; /* Setting associated with the 'Camera' of the device */ "Camera"="Камера"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Сохранять в библиотеке"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Сохранять в «Фотографиях»"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Сохраните копию изображений и видео из приложения MEGA в медиабиблиотеке вашего устройства."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Ключи хранятся у вас"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Разрешить доступ к фотографиям"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Чтобы обмениваться фотографиями и видео, откройте MEGA доступ к фотографиям"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Пожалуйста, дайте приложению MEGA разрешение на доступ к фотографиям для обмена фото и видео."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Включить микрофон и камеру"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Мы хотели бы отправлять вам уведомления, чтобы вы немедленно получали новые сообщения на своем устройстве."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Включить доступ"; +"Allow Access"="Открыть доступ"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Управление файлами"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Все текущие файлы останутся. Только прошлые версии файлов будут удалены."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Вы собираетесь удалить истории версий всех файлов. Любая версия файла, доступная вам от контакта, должна быть удалена им.\n\nОбратите внимание, что текущие файлы не будут удалены."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Планировщик очистки Корзины:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Удалять файлы старше"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Корзина может очищаться автоматически. Минимальный срок — 7 дней."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Чтобы отключить очистку корзины по расписанию или установить более длительный период хранения, нужно подписаться на план PRO."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Закрыть другие сеансы? Будет выполнен выход из системы для всех активных сеансов кроме этого."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Закрыть другие сеансы? Будет выполнен выход из системы для всех активных сеансов кроме этого."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Сохранять в фото"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Сохранение в фото…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Сохранено в фото"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Элемент не сохранён"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Вид списка"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Значки"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ присоединился к вызову."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ покинул вызов."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Плохое соединение."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Активный групповой вызов. Нажмите, чтобы присоединиться."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Коснитесь для возврата к вызову %@"; +/* Menu item to change from grid view to list view */ +"List view"="Вид списка"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Значки"; +/* There are no notifications to display. */ +"No notifications"="Нет оповещений"; +/* The header of a notification related to payments */ +"Payment info"="Платежная информация"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO-подписка скоро истекает"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Уведомление о нарушении"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Объекты восстановлены"; +/* Label shown inside an unseen notification */ +"New"="Новое"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Хочет связаться с вами"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Отменил его запрос на контакт"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Напоминание: У вас запрос на контакт"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Удалил Вас как контакт"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Отношения с контактом установлены"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Аккаунт был удален/отключен"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Заблокировал Вас как контакт"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Вы проигнорировали запрос контакта"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Вы приняли запрос контакта"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Вы отказали в запросе"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Ваш запрос связаться был принят"; +/* When somebody denied your contact request */ +"Denied your contact request"="Отменил ваш запрос"; +/* notification text */ +"A user has left the shared folder {0}"="Пользователь покинул общую папку {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Доступ к папкам был закрыт."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Добавлен 1 файл"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Добавлены файлы(%lld)"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Добавлена 1 папка"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Добавлены папки(%lld)"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Добавлен 1 файл и 1 папка"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Добавлены файлы(%lld) и 1 папка"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Добавлен 1 файл и папки(%lld)"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Добавлены файлы([A]) и папки([B])"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Закрыт доступ к [X] элементам"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Ваша оплата за %1 план получена."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Ваша оплата %1 плана не удалась."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Ваша PRO-подписка истекает через 1 день"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Ваша PRO-подписка истекает через %1 суток(и)."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Ваш элемент с общим доступом (%1 (%2)) убран."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Убранный элемент восстановлен: %1 (%2)."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Разрешать контактам видеть, когда я был активен в MEGA последний раз. Если отключено, вы не сможете увидеть статус активности своих контактов."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Показывать"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="был в сети %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Был в сети очень давно"; +/* */ +"Today"="Сегодня"; \ No newline at end of file diff --git a/iMEGA/Languages/th.lproj/Localizable.strings b/iMEGA/Languages/th.lproj/Localizable.strings index 83d63cf294..de0fd41b1c 100644 --- a/iMEGA/Languages/th.lproj/Localizable.strings +++ b/iMEGA/Languages/th.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="คุณไม่สามารถดาวน์โหลดโฟลเดอร์เปล่าได้"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="บัญชีของคุณได้ทำการเปิดใช้งานแล้ว กรุณาเข้าสู่ระบบ."; +"accountAlreadyConfirmed"="บัญชีของคุณถูกเปิดใช้งานแล้ว กรุณาเข้าสู่ระบบ"; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="ลิงก์ไม่ถูกต้อง"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="กรอกอีเมลให้ถูกต้อง"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="รูปแบบอีเมลไม่ถูกต้อง"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="กรอกรหัสผ่านให้ถูกต้อง"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="อีเมล"; @@ -130,10 +130,8 @@ "passwordStrong"="รหัสผ่านนี้จะสามารถป้องกันการโจมตีแบบ brute-force ที่มีความซับซ้อนได้ กรุณาตรวจสอบอีกครั้งว่าคุณจะสามารถจำรหัสผ่านเหล่านี้ได้"; /* */ "agreeWithTheMEGATermsOfService"="ฉันเห็นด้วยกับเงื่อนไขการให้บริการของ MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="กรอกชื่อให้ถูกต้อง"; -/* */ -"invalidFirstNameAndLastName"="ชื่อและ/หรือนามสกุลไม่ถูกต้อง"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="รหัสผ่านไม่ตรงกัน"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="รอการยืนยันทางอีเมล"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="กรุณาใส่รหัสผ่านเพื่อยืนยันบัญชีของคุณ"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="การยืนยันบัญชีของคุณ"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="ยืนยันบัญชี"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="รหัสผ่านไม่ถูกต้อง"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -677,7 +675,7 @@ /* Option title to enable Camera Uploads only when the device is charging */ "onlyWhenChargingLabel"="เมื่อชาร์จเท่านั้น"; /* Footer text to explain the Camera Uploads functionality */ -"cameraUploads_footer"="เมื่อเปิดใช้งาน รูปถ่ายและวิดีโอใหม่จะถูกอัปโหลด"; +"cameraUploads_footer"="เมื่อเปิดใช้รูปภาพและวิดีโอใหม่จะมีการอัปโหลด"; /* App means “Application” */ "App version"="เวอร์ชันแอป"; /* Title of the label where the SDK version is shown */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="ช่วยเหลือ"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="ศูนย์ช่วยเหลือ"; +"helpCentreLabel"="ศูนย์ความช่วยเหลือ"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="เข้าร่วมเบต้า"; /* Title to rate the app */ "rateUsLabel"="ให้คะแนนเรา"; /* Title of the Transfers section */ @@ -924,9 +922,9 @@ /* Message shown on the achievements dialog for achieved achievements, %1 and %2 are replaced with e.g. 20 GB */ "installMEGASyncCompletedExplanation"="คุณได้รับพื้นที่จัดเก็บ %1 และโควต้าการถ่ายโอน %2 สำหรับการติดตั้ง MEGAsync"; /* */ -"installOurMobileApp"="ติดตั้งแอปบนอุปกรณ์เคลื่อนที่ของเรา"; +"installOurMobileApp"="ติดตั้งแอปของเราบนมือถือ"; /* Message shown on the achievements dialog for achieved achievements, %1 and %2 are replaced with e.g. 20 GB */ -"installOurMobileAppCompletedExplanation"="คุณได้รับพื้นที่จัดเก็บ %1 และโควต้าการถ่ายโอน %2 สำหรับการติดตั้งแอปมือถือของเรา"; +"installOurMobileAppCompletedExplanation"="คุณได้รับพื้นที่จัดเก็บ %1 และโควต้าการถ่ายโอน %2 สำหรับการติดตั้งแอปของเราบนมือถือ"; /* */ "xDaysLeft"="เหลืออีก %1 วัน"; /* %1 will be replaced by a numeric value and %2 will be 'days' or 'months', for example (Expires in [S]10[/S] days) */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="คุณสามารถอัปเกรดภายหลังได้ในบัญชีของฉัน"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]คลาวด์ไดร์ฟเกือบเต็มแล้ว[/S] [A]อัปเกรดเดี๋ยวนี้[/A] เป็นบัญชี PRO และเพิ่มพื้นที่เก็บข้อมูลระบบคลาวด์ได้ถึง [S]4 TB (4096 GB)[/S]"; +"cloudDriveIsAlmostFull"="[S]คลาวด์ไดร์ฟเกือบเต็มแล้ว[/S] [A]อัปเกรดเดี๋ยวนี้[/A] เป็นบัญชี PRO และเพิ่มพื้นที่เก็บข้อมูลระบบคลาวด์ได้ถึง [S]8 TB (8192 GB)[/S]"; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]บัญชีของคุณเต็มแล้ว[/S] [A]อัปเกรดเดี๋ยวนี้[/A] เป็นบัญชี PRO และเพิ่มพื้นที่เก็บข้อมูลระบบคลาวด์ได้ถึง [S]4 TB (4096 GB)[/S]"; +"cloudDriveIsFull"="[S]บัญชีของคุณเต็มแล้ว[/S] [A]อัปเกรดเดี๋ยวนี้[/A] เป็นบัญชี PRO และเพิ่มพื้นที่เก็บข้อมูลระบบคลาวด์ได้ถึง [S]8 TB (8192 GB)[/S]"; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="พื้นที่จัดเก็บข้อมูล"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1576,6 +1574,8 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="การรับรองความถูกต้องด้วยสองปัจจัย คือการรักษาความปลอดภัยชั้นที่สองสำหรับบัญชีของคุณ ซึ่งหมายความว่าแม้จะมีคนรู้จักรหัสผ่านของคุณ พวกเขาจะไม่สามารถเข้าถึงข้อมูลของคุณได้ เพราะต้องใช้รหัสหกหลักสำหรับการรับรองความถูกต้องก่อนทุกครั้ง"; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="คุณต้องมีแอปตัวรับรองความถูกต้องสำหรับอุปกรณ์นี้"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="คุณต้องมีแอปตัวรับรองความถูกต้องเพื่อเปิดใช้งาน 2FA บน MEGA คุณสามารถดาวน์โหลดและติดตั้งแอปพลิเคชัน Google Authenticator, Duo Mobile, Authy หรือ Microsoft Authenticator สำหรับโทรศัพท์หรือแท็บเล็ตของคุณได้"; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="เปิดใช้งานการรับรองความถูกต้องด้วยสองปัจจัยแล้ว"; /* A message on the dialog shown after 2FA was successfully enabled. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="ปิดใช้งานการรับรองความถูกต้องด้วยสองปัจจัยแล้ว"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="สแกนหรือคัดลอกรหัสไปยังแอปตัวรับรองความถูกต้องของคุณ อย่าลืมสำรองรหัสนี้ไว้ในที่ปลอดภัยในกรณีที่คุณทำโทรศัพท์หาย"; +"scanOrCopyTheSeed"="สแกนหรือคัดลอกรหัสไปยังแอปตัวรับรองความถูกต้องของคุณ อย่าลืมสำรองรหัสนี้ไว้ในที่ปลอดภัยในกรณีที่คุณทำอุปกรณ์หาย"; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="อุปกรณ์ตัวรับรองความถูกต้องของคุณสูญหายหรือไม่"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="บันทึกรูปภาพในไลบรารี"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="บันทึกวิดีโอในไลบรารี"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="บันทึกภาพในรูปภาพ"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="บันทึกวิดีโอในรูปภาพ"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="รูปภาพและ/หรือวิดีโอที่ดาวน์โหลดจะถูกเก็บไว้ในไลบรารีสื่อของอุปกรณ์แทนที่จะเป็นส่วนออฟไลน์"; /* Setting associated with the 'Camera' of the device */ "Camera"="กล้อง"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="บันทึกในไลบรารี"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="บันทึกในรูปภาพ"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="บันทึกสำเนาของรูปภาพและวิดีโอที่ถ่ายจากแอป MEGA ในไลบรารีสื่อของอุปกรณ์"; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="คุณคือผู้ถือกุญแจ"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="อนุญาตให้เข้าถึงรูปถ่าย"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="หากต้องการแชร์รูปถ่ายและวิดีโอ ต้องอนุญาตให้ MEGA เข้าถึงรูปถ่ายของคุณ"; +"Please give the MEGA App permission to access Photos to share photos and videos."="กรุณาให้สิทธิ์แอป MEGA ในการเข้าถึงรูปภาพเพื่อแชร์รูปภาพและวิดีโอ"; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="เปิดใช้งานไมโครโฟนและกล้องถ่ายรูป"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="เราต้องการส่งการแจ้งเตือนเพื่อให้คุณได้รับข้อความใหม่บนอุปกรณ์ของคุณทันที"; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="อนุญาต"; +"Allow Access"="อนุญาตให้เข้าถึง"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="การจัดการไฟล์"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="ไฟล์ปัจจุบันทั้งหมดจะยังคงอยู่ ระบบจะลบไฟล์เวอร์ชันที่เก่ากว่าเท่านั้น"; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="คุณกำลังจะลบประวัติเวอร์ชันของไฟล์ทั้งหมด ไฟล์ใด ๆ ที่แชร์ให้กับคุณจากผู้ติดต่อจะต้องถูกลบทิ้งด้วย\n\nโปรดทราบว่าไฟล์ปัจจุบันจะไม่ถูกลบ"; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="ตารางการทำความสะอาดถังขยะ:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="ลบไฟล์ที่เก่ากว่า"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="ถังขยะสามารถทำความสะอาดให้คุณโดยอัตโนมัติ ระยะเวลาขั้นต่ำ 7 วัน"; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="ในการปิดใช้งานตัวกำหนดการทำความสะอาดถังขยะหรือกำหนดระยะเวลาการเก็บรักษาไฟล์ในถังขยะ คุณต้องสมัครแผน PRO"; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="คุณต้องการปิดเซสชันอื่นทั้งหมดหรือไม่ การดำเนินการนี้จะนำคุณออกจากเซสชันที่มีการใช้งานอื่น ๆ ทั้งหมดยกเว้นในปัจจุบัน"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="คุณต้องการปิดเซสชันอื่นทั้งหมดหรือไม่ การดำเนินการนี้จะนำคุณออกจากเซสชันที่มีการใช้งานอื่น ๆ ทั้งหมดยกเว้นในปัจจุบัน"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="บันทึกลงในรูปถ่าย"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="กำลังบันทึกไปยังรูปภาพ..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="บันทึกไปยังรูปภาพแล้ว"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="ไม่สามารถบันทึกรายการได้"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="มุมมองรายการ"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="มุมมองภาพตัวอย่าง"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ เข้าร่วมสายแล้ว"; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ วางสายไปแล้ว"; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="การเชื่อมต่อไม่ดี"; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="มีการโทรกลุ่มอยู่ในขณะนี้ แตะเพื่อเข้าร่วม"; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="แตะเพื่อกลับไปที่สาย %@"; +/* Menu item to change from grid view to list view */ +"List view"="มุมมองรายการ"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="มุมมองภาพตัวอย่าง"; +/* There are no notifications to display. */ +"No notifications"="ไม่มีแจ้งเตือน"; +/* The header of a notification related to payments */ +"Payment info"="ข้อมูลการชำระเงิน"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="แผนสมาชิกแบบ PRO จะหมดอายุเร็ว ๆนี้"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="แจ้งให้ลบเนื้อหา"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="คืนสิทธิ์การลบเนื้อหา"; +/* Label shown inside an unseen notification */ +"New"="ใหม่"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="ส่งคำขอสำหรับรายการติดต่อแล้ว"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="ยกเลิกคำขอผู้ติดต่อของพวกเขาแล้ว"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="แจ้งเตือน: คุณมีคำขอผู้ติดต่อใหม่"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="ลบคุณออกจากผู้ติดต่อ"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="ความสัมพันธ์ของผู้ติดต่อที่สร้างขึ้น"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="บัญชีได้ถูกลบ/ปิดใช้งานแล้ว"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="บล็อกคุณจากผู้ติดต่อ"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="คุณละเว้นคำขอผู้ติดต่อแล้ว"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="คุณยอมรับคำขอผู้ติดต่อแล้ว"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="คุณปฏิเสธคำขอผู้ติดต่อ"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="ยอมรับคำขอผู้ติดต่อของคุณแล้ว"; +/* When somebody denied your contact request */ +"Denied your contact request"="ปฏิเสธคำขอผู้ติดต่อแล้ว"; +/* notification text */ +"A user has left the shared folder {0}"="ผู้ใช้ออกจากโฟลเดอร์ที่ใช้ร่วมกัน {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="การเข้าถึงโฟลเดอร์ถูกลบออกแล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="เพิ่ม 1 ไฟล์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="เพิ่ม %lld ไฟล์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="เพิ่ม 1 โฟลเดอร์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="เพิ่ม %lld โฟลเดอร์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="เพิ่ม 1 ไฟล์และ 1 โฟลเดอร์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="เพิ่ม %lld ไฟล์และ 1 โฟลเดอร์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="เพิ่ม 1 ไฟล์และ %lld โฟลเดอร์แล้ว"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="เพิ่ม [A] ไฟล์และ [B] โฟลเดอร์แล้ว"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="ได้เอา [X] รายการออกจากการแชร์"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="การชำระเงินสำหรับแผน %1 ได้รับเรียบร้อยแล้ว"; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="การชำระเงินสำหรับแผน %1 ไม่สมบูรณ์"; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="แผนสมาชิกแบบ PRO ของคุณจะหมดอายุใน 1 วัน"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="แผนสมาชิกแบบ PRO ของคุณจะหมดอายุใน %1 วัน"; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="การแชร์สาธารณะของคุณ %1 (%2) ถูกแจ้งว่าให้ลบเนื้อหา"; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="การแจ้งให้ดำเนินการของคุณ %1 (%2) ถูกคืนสิทธิ์เรียบร้อยแล้ว"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="อนุญาตให้ผู้ติดต่อของคุณเห็นว่าเป็นครั้งสุดท้ายที่คุณใช้งาน MEGA หากปิดใช้งานคุณจะไม่สามารถดูสถานะการทำงานของผู้ติดต่อของคุณได้"; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="แสดง"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="ดูครั้งล่าสุด %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="ดูครั้งล่าสุดเมื่อนานมาแล้ว"; +/* */ +"Today"="วันนี้"; \ No newline at end of file diff --git a/iMEGA/Languages/tl.lproj/Localizable.strings b/iMEGA/Languages/tl.lproj/Localizable.strings index 65ea389bcb..c21ad7ae6f 100644 --- a/iMEGA/Languages/tl.lproj/Localizable.strings +++ b/iMEGA/Languages/tl.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Hindi ka pupwedeng mag-download ng bakanteng folder"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Ang inyong account ay aktibo na. Mag-log in na."; +"accountAlreadyConfirmed"="Ang account mo ay activated na. Mag-log in na."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Maling link"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Maglagay ng valid email"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Mayroon yatang di-mawaring email"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Maglagay ng valid password"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Ang password na ito ay matatagalan ang mas matiniding atake ng mga hackers. Siguraduhin lamang na matatandaan mo ito."; /* */ "agreeWithTheMEGATermsOfService"="Pumapayag ako sa Terms of Service ng MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Maglagay ng valid na pangalan"; -/* */ -"invalidFirstNameAndLastName"="Maling pangalan o apelyedo"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Di tugma ang password"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Hinihintay ang kumpirmasyon sa email"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Ilagay ang inyong password para makumpirma ang inyong account"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="I-confirm inyong account"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Kumpirmahin ang account"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Maling password"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -223,7 +221,7 @@ /* Button title to allow the user postpone an action */ "later"="Later"; /* Title of one of the filters in 'Contacts requests' section. If 'Received' is selected, it will only show the requests which have been recieved. */ -"received"="Received"; +"received"="Natanggap"; /* Title of one of the filters in 'Contacts requests' section. If 'Sent' is selected, it will only show the requests which have been sent out. */ "sent"="Naipadala"; /* Success message shown when you acepted a contact request */ @@ -235,7 +233,7 @@ /* Title shown when there's no pending contact requests */ "noRequestPending"="Walang pending na request"; /* Title shown when the transfers are paused */ -"transfersEmptyState_titlePaused"="Itigil muna ang Paglilipat"; +"transfersEmptyState_titlePaused"="Paused na Transfers"; /* Title shown when the there is not any transfer and they are not paused */ "transfersEmptyState_titleAll"="Walang Transfer"; /* Title shown when the there is not any transfer, and the filter "Download" option is selected */ @@ -377,7 +375,7 @@ /* Title for a button to copy the link to the clipboard */ "copyLink"="Kopyahin ang link"; /* Title for a button that copies the key of the link to the clipboard */ -"copyKey"="Copy key"; +"copyKey"="I-copy ang key"; /* Title shown under the action that allows you to open a file in another app */ "openIn"="Buksan sa..."; /* */ @@ -387,11 +385,11 @@ /* */ "sendToContact"="Magpadala ng contact"; /* Toast text upon sending a single file to chat */ -"fileSentToChat"="File sent to chat"; +"fileSentToChat"="Naipadala na file sa chat"; /* Success message when the attachment has been sent to a many chats */ "fileSentToXChats"="File sent to %1$d chats"; /* Toast text upon sending multiple files to chat */ -"filesSentToChat"="Files sent to chat"; +"filesSentToChat"="Naipadala na ang mga files sa chat"; /* success message when sending multiple files. Please do not modify the %d placeholder. */ "xfilesSentSuccesfully"="%d files sent successfully"; /* Message shown when there is an active link that can be removed or disabled */ @@ -515,7 +513,7 @@ /* Success message shown when Camera Uploads has been enabled */ "cameraUploadsEnabled"="Pinagana ang Camera Uploads"; /* Message shown when the camera uploads have been completed */ -"cameraUploadsComplete"="Camera uploads complete"; +"cameraUploadsComplete"="Tapos na ang Camera uploads"; /* Message shown while uploading files. Singular. */ "cameraUploadsPendingFile"="Upload in progress, 1 file ang pending"; /* Message shown while uploading files. Plural. */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="Ayuda"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Tulong"; +"helpCentreLabel"="Help Centre"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Sumama sa Beta"; /* Title to rate the app */ "rateUsLabel"="I-rate kami"; /* Title of the Transfers section */ @@ -876,19 +874,19 @@ /* Title of My Account section. There you can see your account details */ "myAccount"="Aking Account"; /* Button title that shows the different MEGA PRO plans to which you can upgrade. (String as short as possible) */ -"buyPRO"="Buy PRO"; +"buyPRO"="Bumili ng PRO"; /* Caption of a button to upgrade the account to Pro status */ "upgrade"="I-upgrade"; /* Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile */ -"viewAndEditProfile"="View and edit profile"; +"viewAndEditProfile"="I-view at i-edit ang profile"; /* Label for any 'Profile' button, link, text, title, etc. - (String as short as possible). */ "profile"="Profile"; /* Title of the Achievements section */ "achievementsTitle"="Mga Achievements"; /* Title of the Used storage section */ -"usedStorage"="Used storage"; +"usedStorage"="Used na storage"; /* Subtitle show on My Account view under the 'Achievements' title to give more details of the section that will appear if tapped 2/2 */ -"inviteFriendsAndGetRewards"="Invite friends and get bonuses"; +"inviteFriendsAndGetRewards"="Imbitahan ang mga kaibigan at makakuha ng bonuses"; /* Indicating text for when "you invite your friends" */ "inviteYourFriends"="I-invite ang kaibigan mo"; /* title of the introduction for the achievements screen */ @@ -1208,9 +1206,9 @@ /* A typing indicator in the chat. Please leave the %@ which will be automatically replaced with the user's name at runtime. */ "isTyping"="Nagtatype si %@ ..."; /* Plural, a hint that appears when two users are typing in a group chat at the same time. The parameter will be the concatenation of both user names. The tags and placeholders shouldn't be translated or modified. */ -"twoUsersAreTyping"="%1$s [A]are typing…[/A]"; +"twoUsersAreTyping"="Sina %1$s [A]ay mga nagtata-type…[/A]"; /* text that appear when there are more than 2 people writing at that time in a chat. For example User1, user2 and more are typing... The parameter will be the concatenation of the first two user names. The tags and placeholders shouldn't be translated or modified. */ -"moreThanTwoUsersAreTyping"="%1$s [A]and more are typing…[/A]"; +"moreThanTwoUsersAreTyping"="Sina %1$s [A]at iba ay mga nagta-type…[/A]"; /* This is shown in the typing area in chat, as a placeholder before the user starts typing anything in the field. The format is: Write a message to Contact Name... Write a message to "Chat room topic"... Write a message to Contact Name1, Contact Name2, Contact Name3 */ "writeAMessage"="Write a message to %s…"; /* Information if there are no history messages in current chat conversation */ @@ -1352,9 +1350,9 @@ "languageRestartNotification"="Language changed. Tap to restart."; /* The following strings are only for the App Store description, they're not used on the app (The only exception is the one with 'autorenewableDescription' key) On the 'generalDescriptionParagraph6' we must delete 'Desktop - https://mega.nz/' because the app can be rejected because of that. */ /* Paragraph 1 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph1"="MEGA provides user-controlled encrypted cloud storage through standard web browsers, together with dedicated apps for mobile devices. Unlike other cloud storage providers, your data is encrypted and decrypted by your client devices only and never by us."; +"generalDescriptionParagraph1"="Nagbibigay ang MEGA ng user-controlled encrypted cloud storage sa pamamagitan ng standard web browser, kasama ang dedicated apps para sa mobile devices. Di gaya ng ibang cloud storage providers, ang iyong data ay encrypted at decrypted ng devices na hawak mo at hindi namin."; /* Paragraph 2 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ -"generalDescriptionParagraph2"="Upload your files from your smartphone or tablet, then search, store, download, stream, view, share, rename or delete your files any time, from any device, anywhere. Share folders with your contacts and see their updates in real time."; +"generalDescriptionParagraph2"="I-upload ang inyong files galing sa inyong smartphone o tablet, at mag-search, mag-store, mag-download, i-view, i-share, i-rename o burahin ang iyong files kahit kailan, sa kahit anong device, kahit saan. Ibahagi ang folders sa inyong contacts at makita ang updates ng real time."; /* Paragraph 3 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. */ "generalDescriptionParagraph3"="Ang encryption process ay nangangahulugan na hindi namin na-aaccess o na-rereset ang iyong password kaya't KINAKAILANGAN mo itong tandaan (unless na mayroon kang Recovery Key na naka-back up) o mawawalan ka ng access sa iyong stored files."; /* Paragraph between 3rd and 4th of the general description of MEGA shown on the iOS App Store, part of Android string 17769 */ @@ -1367,7 +1365,7 @@ /* Paragraph 6 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. Please keep the multiple line format. */ "generalDescriptionParagraph6"="For more info, please check our website: https://mega.nz/\nTerms of Service: https://mega.nz/terms\nPrivacy Policy: https://mega.nz/privacy"; /* Label shown when you receive an incoming call, before start the call. */ -"calling..."="Calling…"; +"calling..."="Tumatawag..."; /* Notification text body shown when you have missed one audio call */ "missedAudioCall"="Missed audio call"; /* Notification text body shown when you have missed several audio calls. [A] = {number of missed audio calls} */ @@ -1405,19 +1403,19 @@ /* Footer that explains the way Auto-Accept works for QR codes */ "autoAcceptFooter"="Contacts that scan your QR Code will be automatically added to your contact list."; /* Action to reset the current valid QR code of the user */ -"resetQrCode"="Reset QR Code"; +"resetQrCode"="I-reset ang QR Code"; /* Footer that explains what would happen if the user resets his/her QR code */ "resetQrCodeFooter"="Ang datihang QR codes ay hindi na valid."; /* Title for view that displays the QR code of the user. String as short as possible. */ -"myCode"="My Code"; +"myCode"="Ang Aking QR Code"; /* Segmented control title for view that allows the user to scan QR codes. String as short as possible. */ -"scanCode"="Scan Code"; +"scanCode"="I-scan ang Code"; /* Label that encourage the user to line the QR to scan with the camera */ -"lineCodeWithCamera"="Line up the QR code to scan it with your device’s camera"; +"lineCodeWithCamera"="Itama ang QR code sa camera ng iyong device para ma-scan ito"; /* Error text shown when the user scans a QR that is not valid. String as short as possible. */ -"invalidCode"="Invalid code"; +"invalidCode"="Maling code"; /* Success text shown in a label when the user scans a valid QR. String as short as possible. */ -"codeScanned"="Code scanned"; +"codeScanned"="Na-scan na ang code"; /* title of the screen that shows the users with whom the user has shared a folder */ "sharedWidth"="Ibahagi kasama si:"; /* */ @@ -1501,7 +1499,7 @@ /* Button title to allow the user open the default browser */ "openBrowser"="Buksan ang browser"; /* A label for the Restart button to relaunch MEGAsync. */ -"restart"="Restart"; +"restart"="I-restart"; /* Title to alert user the possibility of resume playing the video or start from the beginning */ "resumePlayback"="Resume playback?"; /* Message to show the user info (name and time) about the resume of the video */ @@ -1513,35 +1511,35 @@ /* Title of the notification for a missed call */ "missedCall"="Lumipas na tawag"; /* When an outgoing call of user A with user B had been rejected by user B */ -"callWasRejected"="Call was rejected"; +"callWasRejected"="Na-reject ang tawag"; /* When an active call of user A with user B had cancelled */ -"callWasCancelled"="Call was cancelled"; +"callWasCancelled"="Nakansela ang tawag"; /* When an active call of user A with user B had not answered */ -"callWasNotAnswered"="Call was not answered"; +"callWasNotAnswered"="Hindi nasagot ang tawag"; /* When an active call of user A with user B had failed */ -"callFailed"="Call failed"; +"callFailed"="Nabigo ang pagtawag"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Nagtapos ang pagtawag"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ "xSeconds"="%d seconds"; /* */ -"1Second"="1 second"; +"1Second"="1 segundo"; /* */ -"1Hour"="1 hour"; +"1Hour"="1 oras"; /* */ -"xHours"="%d hours"; +"xHours"="%d na oras"; /* */ -"1Hour1Minute"="1 hour, 1 minute"; +"1Hour1Minute"="1 oras, 1 minuto"; /* */ -"1HourxMinutes"="1 hour, %d minutes"; +"1HourxMinutes"="1 oras, %d minutes"; /* */ -"xHours1Minute"="%d hours, 1 minute"; +"xHours1Minute"="%d na oras, 1 minuto"; /* */ -"xHoursxMinutes"="%1$d hours, %2$d minutes"; +"xHoursxMinutes"="%1$d na oras, %2$d na minuto"; /* Alert title shown when users try to stream an unsupported audio/video file */ -"fileNotSupported"="File not supported"; +"fileNotSupported"="Hindi suportado ang file"; /* Alert message shown when users try to stream an unsupported audio/video file */ "message_fileNotSupported"="You can save it for Offline and open it in a compatible app."; /* Item of a menu to forward a message chat to another chatroom */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,66 +1574,68 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ -"pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; +"pleaseSaveYourRecoveryKey"="Ilagay ang iyong Recovery Key sa isang safe location"; /* An informational message on the Backup Recovery Key dialog. */ "twoFactorAuthenticationEnabledWarning"="If you lose access to your account after enabling 2FA and you have not backed up your Recovery Key, MEGA cannot help you gain access to it again."; /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ -"lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +"lostYourAuthenticatorDevice"="Nawala ang iyong Authenticator device?"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Mag-save ng Images sa Library"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Mag-save ng Videos sa Library"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ -"Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="I-save sa Library"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ +"Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Magtabi ng copy ng images at videos na nakuha sa MEGA app ng inyong media library sa device mo"; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ -"You hold the keys"="You hold the keys"; +"You hold the keys"="Hawak mo ang susi"; /* Description shown in a page of the onboarding screens explaining the encryption paradigm */ -"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."; +"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files."="Nanatili kami dahil sa seguridad para mapangalagaan ang inyong mga files sa likod ng isang bantay saradong pader na ikaw lamang maka-aaccess."; /* Title shown in a page of the on boarding screens explaining that the chat is encrypted */ -"Encrypted chat"="Encrypted chat"; +"Encrypted chat"="Encrypted na chat"; /* Description shown in a page of the onboarding screens explaining the chat feature */ -"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."="Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."; +"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."="Fully encrypted na chat na may voice at video calls, group messaging at file sharing integration sa iyong Cloud Drive."; /* Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate */ -"Create your Network"="Create your Network"; +"Create your Network"="Gumawa ng sariling Network"; /* Description shown in a page of the onboarding screens explaining contacts */ -"Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"="Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"; +"Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA"="Magdagdag ng contacts, gumawa ng network, mag-collaborate, gumawa ng voice at video calls ng hindi umaalis sa MEGA"; /* Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically */ -"Your Photos in the Cloud"="Your Photos in the Cloud"; +"Your Photos in the Cloud"="Ang iyong Photos sa Cloud"; /* Description shown in a page of the onboarding screens explaining the camera uploads feature */ -"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."; +"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now."="Ang Camera Uploads ay isang essential feature ng kahit anong mobile device at covered ka namin. Gumawa ka ng sariling account ngayon."; /* Button which triggers the initial setup */ -"Setup MEGA"="Setup MEGA"; +"Setup MEGA"="I-setup ang MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ -"To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +"To fully take advantage of your MEGA account we need to ask you some permissions."="Para magkaroon ng buong advantage sa iyong MEGA account kailangan naming hingin ang inyong permiso"; /* Title label that explains that the user is going to be asked for the photos permission */ -"Allow Access to Photos"="Allow Access to Photos"; +"Allow Access to Photos"="Bigyan ng Access sa Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ -"Enable Microphone and Camera"="Enable Microphone and Camera"; +"Enable Microphone and Camera"="I-enable ang Microphone at Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ -"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"; +"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="Para makagawa ng encrypted voice at video calls, bigyan ng access ang MEGA sa iyong Camera at Microphone"; /* Title label that explains that the user is going to be asked for the notifications permission */ "Enable Notifications"="Paganahin ang Notifications"; /* Detailed explanation of why the user should give permission to deliver notifications */ -"We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; +"We would like to send you notifications so you receive new messages on your device instantly."="Gusto naming magpadala ng notifications para mareceive ang bagong messages sa iyong device kaagad."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="I-enable ang Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="File Management"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="All current files will remain. Only historic versions of your files will be deleted."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Scheduler ng Paglilinis ng Basurahan:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Tanggalin ang files na lagpas"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="I-save sa Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving sa Photos.."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Na-isaved na sa Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Hindi ma-save ang item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Kuhang palista"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Thumbnail view"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="Kuhang palista"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Thumbnail view"; +/* There are no notifications to display. */ +"No notifications"="Walang notification"; +/* The header of a notification related to payments */ +"Payment info"="Impormasyon tungkol sa pagbabayad"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Malapit ng mawala ang iyong PRO membership plan"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Paalala sa takedown"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Binalik ang takedown"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Ipadala ang inyong contact request"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Itinigil ang contact request"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Paalala: mayroon kang contact request"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Tinanggal ka bilang contact"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Kayo ngayon ay parehas na bagong magka-contact"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Ang account ay pinahi o tinanggal"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Hinarang ka bilang contact"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Hindi mo pinansin ang contact request"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Tinanggap mo ang contact request"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Tinanggihan mo ang contact request"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Tinggap ang inyong contact request"; +/* When somebody denied your contact request */ +"Denied your contact request"="Tinaggihan ang inyong contact request"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Tinanggal ang access sa folder."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Natanggal [X] ang items sa binahagi"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Ang inyong bayad para sa %1 plano ay tagumpay."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Ang inyong bayad para sa %1 plano ay nabigo."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Ang iyong PRO membership plan mo ay mawawala na sa loob ng isang araw."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Mawawala na ang inyong PRO membership sa loob ng %1 na araw."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Ang binahagi mo sa lahat na %1 (%2) ay tinanggal na."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Ang inyong taken down na %1 (%2) ay muling naibalik."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Ngayon"; \ No newline at end of file diff --git a/iMEGA/Languages/tr.lproj/Localizable.strings b/iMEGA/Languages/tr.lproj/Localizable.strings index c4ffbde3f9..ec5632989f 100644 --- a/iMEGA/Languages/tr.lproj/Localizable.strings +++ b/iMEGA/Languages/tr.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Boş bir klasörü indiremezsiniz"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Hesabınız aktive edildi. Lütfen giriş yapın."; +"accountAlreadyConfirmed"="Hesabınız etkinleştirildi. Lütfen giriş yapın."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Geçersiz bağlantı"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Geçerli bir e-posta girin"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="E-posta adresi biçimi geçersiz"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Geçerli bir parola girin"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Eposta"; @@ -130,10 +130,8 @@ "passwordStrong"="Bu parola en karmaşık kaba kuvvet saldırılarına karşı gelecektir. Lütfen hatırlayacağınızdan emin olun."; /* */ "agreeWithTheMEGATermsOfService"="MEGA Hizmet Koşullarını kabul ediyorum"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Geçerli bir isim girin"; -/* */ -"invalidFirstNameAndLastName"="Geçersiz ad/soyad"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Parolalar eşleşmiyor"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Eposta onayı bekleniyor."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Hesabınızı onaylamak için parolanızı girin"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Hesabınızı onaylayın."; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Hesabı onaylayın"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Hatalı parola"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="Yardım"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Destek Merkezi"; +"helpCentreLabel"="Yardım Merkezi"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="Beta'ya Katıl"; /* Title to rate the app */ "rateUsLabel"="Bizi değerlendirin"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Daha sonra Hesabım sekmesinden yükseltebilirsiniz"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Bulut Sürücü neredeyse dolu.[/S] [A]Şimdi PRO hesabına yükseltin[/A] ve [S]4TB'a (4096 GB)[/S] kadar bulut depolama alanı sahip olun."; +"cloudDriveIsAlmostFull"="[S]Bulut sürücü neredeyse dolu.[/S] [A]PRO hesaba hemen yükseltin[/A] ve [S]8 TB (8192 GB)’a kadar[/S] bulut depolama alanına sahip olun."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Hesabınız dolu.[/S] [A]Şimdi PRO hesaba yükseltin[/A] ve [S]4TB'a varan (4096 GB)[/S] bulut depolamasına sahip olun."; +"cloudDriveIsFull"="[S]Hesabınız dolu.[/S] [A]Hemen bir PRO hesabına geçin[/A] ve [S]8 TB (8192 GB)’a kadar[/S] bulut depolama alanına ulaşın."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Saklama alanı"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1004,7 +1002,7 @@ /* Text shown in the purchase plan view to explain that annual subscription is 17% cheaper than 12 monthly payments */ "twoMonthsFree"="Yıllık abonelik, 12 aylık ödemeye göre %17 daha ucuzdur"; /* Text shown on the upgrade account page above the current PRO plan subscription */ -"currentPlan"="Mevcut Plan:"; +"currentPlan"="Mevcut Tarife:"; /* Button on the Pro page to request a custom Pro plan because their storage usage is more than the regular plans. */ "requestAPlan"="Bir plan talep et"; /* Asks the user to request a custom Pro plan from customer support because their storage usage is more than the regular plans. */ @@ -1102,7 +1100,7 @@ /* Used in the \"rich previews\", when the user first tries to send an url - we ask them before we generate previews for that URL, since we need to send them unencrypted to our servers. */ "richPreviewsFooter"="MEGAchat deneyiminizi geliştirin. URL içeriği uçtan uca şifreleme olmadan alınacaktır."; /* Section title of a button where you can enable mobile data for voice and video calls. */ -"voiceAndVideoCalls"="Sesli ve görüntülü arama"; +"voiceAndVideoCalls"="Sesli ve Görüntülü Arama"; /* Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature. */ "useMobileData"="Mobil Veriyi Kullan"; /* A header in the Chat settings. This changes the user's status to Away after some time of inactivity. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Kurulumu Başlat"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Lütfen Kimlik Doğrulama uygulamanızın oluşturduğu 6 basamaklı kodu girin."; +"pleaseEnterTheSixDigitCode"="Lütfen Kimlik Doğrulayıcı uygulamanızın oluşturduğu 6 basamaklı kodu girin."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Neden İki Faktörlü Doğrulamaya ihtiyacınız var?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="İki Faktörlü Kimlik Doğrulama, hesabınızın ikinci bir güvenlik katmanıdır. Başka bir deyişle, birisi şifrenizi biliyor olsa bile, sadece sizin erişebildiğiniz altı haneli koda erişimi olmadan hesabınıza erişemez."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Bu cihazda iki faktörlü bir kimlik doğrulama uygulamasına ihtiyacınız var"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="MEGA'da İki Faktörlü Doğrulamayı etkinleştirmek için kimlik doğrulama uygulamasına ihtiyacınız var. Telefonunuza veya tabletinize Google Authenticator, Duo Mobile, Authy veya Microsoft Authenticator uygulamasını indirip kurabilirsiniz."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="İki Faktörlü Kimlik Doğrulama Etkin"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Bir dahaki sefere hesabınıza giriş yaptığınızda, kimlik doğrulama uygulamanız tarafından sağlanan 6 basamaklı bir kod girmeniz istenecektir."; +"twoFactorAuthenticationEnabledDescription"="Bir dahaki sefere hesabınıza giriş yaptığınızda, Kimlik Doğrulayıcı uygulamanız tarafından sağlanan 6 basamaklı bir kod girmeniz istenecektir."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Lütfen Kurtarma Anahtarınızı güvenli bir yere kaydedin"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="İki Faktörlü Kimlik Doğrulama Devre Dışı"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Kaynak kodunu Kimlik Doğrulama uygulamanıza tarayın veya kopyalayın. Telefonunuzu kaybetme ihtimalinize karşın bu kaynak kodunu güvenli bir yere yedeklediğinizden emin olun."; +"scanOrCopyTheSeed"="Kaynak kodunu Kimlik Doğrulama uygulamanıza tarayın veya kopyalayın. Cihazınızı kaybetme ihtimalinize karşın bu kaynak kodunu güvenli bir yere yedeklediğinizden emin olun."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Authenticator cihazınızı mı kaybettiniz?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Görüntüleri Kütüphaneye Kaydet"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Videoları Kütüphaneye Kaydet"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Görüntüleri Fotoğraflara Kaydet"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Videoları Fotoğraflara kaydet"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="İndirilen görüntüler ve/veya videolar çevrimdışı bölümü yerine cihazınızın medya kütüphanesine indirilecektir."; /* Setting associated with the 'Camera' of the device */ "Camera"="Kamera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Kütüphaneye Kaydet"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Fotoğraflara Kaydet"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="MEGA uygulamasından çekilen görüntü ve videoların bir kopyasını cihazınızın medya kütüphanesine kaydedin."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="Anahtarlar sizdedir"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Resimlere Erişime İzin Ver"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="Resim ve video paylaşmak için, MEGA'nın resimlerinize erişmesine izin verin."; +"Please give the MEGA App permission to access Photos to share photos and videos."="Fotoğraf veya video paylaşmak için, MEGA'nın Fotoğraflarınıza erişimine izin verin."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Mikrofon ve Kamerayı Etkinleştir"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="Yeni mesajları anında alabilmeniz için size bildirim göndermek istiyoruz."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Erişimi Etkinleştir"; +"Allow Access"="Erişime İzin Ver"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Dosya yönetimi"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Şu anki dosyaların tümü kalacaktır. Sadece dosyalarınızın eski sürümleri silinecektir."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="Şu ana kadar oluşturulan tüm dosya sürüm geçmişlerini silmek üzeresiniz. Kişileriniz tarafından paylaşılan dosya sürümleri kendileri tarafından silinmelidir.\n\nDosyaların şimdiki sürümünün silinmeyeceğini unutmayın."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Çöp Kutusu Temizleme Zamanlayıcısı:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Daha eski dosyaları kaldır"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Çöp kutusu sizin yerinize otomatik olarak temizlenebilir. Asgari süre 7 gündür."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="Çöp kutusu temizleyicisini devre dışı bırakmak veya daha uzun saklama süreci seçmek için bir PRO tarifesine abone olmanız gerekiyor."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Diğer tüm oturumları kapatmak istiyor musunuz? Bu, geçerli oturumunuz hariç diğer tüm aktif oturumlarınızı kapatacaktır."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Diğer tüm oturumları kapatmak istiyor musunuz? Bu, geçerli oturumunuz hariç diğer tüm aktif oturumlarınızı kapatacaktır."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Fotoğraflarıma kaydet"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Fotoğraflarıma kaydediliyor..."; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Fotoğraflarıma kaydet"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Öğe kaydedilemedi"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Liste görünümü"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Küçük resim görünümü"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ aramaya katıldı."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ konuşmadan ayrıldı."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Zayıf bağlantı."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="Aktif grup araması var. Katılmak için dokunun."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Konuşmaya dönmek için dokunun %@"; +/* Menu item to change from grid view to list view */ +"List view"="Liste görünümü"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Küçük resim görünümü"; +/* There are no notifications to display. */ +"No notifications"="Bildirim yok"; +/* The header of a notification related to payments */ +"Payment info"="Ödeme bilgileri"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO üyeliğinizin süresi yakında dolacak"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Yayından kaldırma bildirimi"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Yayından kaldırma geri alındı"; +/* Label shown inside an unseen notification */ +"New"="Yeni"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Size bir kişi isteği gönderdi"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="İletişim isteklerini iptal etti"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Hatırlatma: Bir kişi isteğiniz var"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Sizi kişilerinden sildi"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Kişi ilişkisi kuruldu"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Hesap silinmiş/devre dışı"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Kişi olarak sizi engelledi"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Kişi isteğini gözardı ettiniz."; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Kişi isteğini kabul ettiniz"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Kişi isteğini reddettiniz"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="İletişim isteği kabul edildi"; +/* When somebody denied your contact request */ +"Denied your contact request"="İletişim isteğini reddetti"; +/* notification text */ +"A user has left the shared folder {0}"="Bir kullanıcı {0} paylaşımlı klasörden ayrıldı"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Klasörlere erişim kaldırıldı."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="1 dosya eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="%lld dosya eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="1 klasör eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="%lld klasör eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="1 dosya ve 1 klasör eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="%lld dosya ve 1 klasör eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="1 dosya ve %lld klasör eklendi"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="[A] dosya ve [B] klasör eklendi"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Paylaşılanlardan [X] nesne silindi"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="%1 planı için ödemeniz alındı."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="%1 planı için ödemeniz başarısız oldu."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="PRO üyeliğinizin süresi 1 gün içerisinde dolacak"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="PRO üyeliğinizin süresi %1 gün içerisinde dolacak"; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Herkese açık olarak paylaşılan %1 (%2), telif hakkı nedeniyle yayından kaldırıldı."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Telif hakkı nedeniyle yayından kaldırılan %1 (%2), tekrar yayına alındı."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Kişilerinizin MEGA'daki son görülme zamanınızı görmesine izin verin. Devre dışı bırakıldığında kişilerinizin etkinlik durumunu göremeyeceksiniz."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Göster"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Son görülme %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Son görülme uzun zaman önce"; +/* */ +"Today"="Bugün"; \ No newline at end of file diff --git a/iMEGA/Languages/uk.lproj/Localizable.strings b/iMEGA/Languages/uk.lproj/Localizable.strings index 184ae68f2e..8ac272319a 100644 --- a/iMEGA/Languages/uk.lproj/Localizable.strings +++ b/iMEGA/Languages/uk.lproj/Localizable.strings @@ -102,7 +102,7 @@ "emailInvalidFormat"="Введіть дійсну адресу е-пошти"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Невірний формат електронної адреси"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Введіть правильний пароль"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Е-пошта"; @@ -130,10 +130,8 @@ "passwordStrong"="Цей пароль зможе протистояти найбільш типовим атакам підбору паролів. Будь ласка, переконайтеся, що ви будете пам'ятати його."; /* */ "agreeWithTheMEGATermsOfService"="Я погоджуюсь з Умовами користування MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Введіть дійсне ім'я"; -/* */ -"invalidFirstNameAndLastName"="Неправильне ім'я та/або прізвище."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Паролі не співпадають"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Очікування підтвердження е-пошти."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Будь ласка, введіть пароль, щоб підтвердити обліковий запис"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Підтвердження облікового запису"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Підтвердити акаунт"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Невірний пароль"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -1521,7 +1519,7 @@ /* When an active call of user A with user B had failed */ "callFailed"="Call failed"; /* When an active call of user A with user B had ended */ -"callEnded"="Call ended."; +"callEnded"="Call ended"; /* Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) */ "duration"="Duration: %@"; /* */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Two-Factor Authentication Enabled"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Two-Factor Authentication Disabled"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Управління файлами"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="All current files will remain. Only historic versions of your files will be deleted."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Планувальник очищення кошика для сміття:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Видалити файли старше"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Do you want to close all other sessions? This will log you out on all other active sessions except the current one."; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Перегляд списком"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Ескізи"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="Перегляд списком"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Ескізи"; +/* There are no notifications to display. */ +"No notifications"="Повідомлень немає"; +/* The header of a notification related to payments */ +"Payment info"="Інформація оплати"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="PRO членство скоро закінчиться"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Повідомлення про вилучення"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Відновлення доступу"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Вам надіслано запит на контакт"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Контактний запис скасовано"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Нагадування: у вас є запит контакту"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Ваш контакт видалено"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Тепер ви у контактах один одного"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Обліковий запис було видалено/деактивовано"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Ваш контакт заблоковано"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Ви проігнорували запит контакту"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Ви прийняли запит контакту"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Ви відмовили у запиті контакту"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Прийняти запит від контакту"; +/* When somebody denied your contact request */ +"Denied your contact request"="Відмовити в запиті від контакту"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Доступ до тек було припинено."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Вилучено [X] елементів зі спільного доступу"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Ваш платіж за план %1 було успішно отримано."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Ваш платіж за план %1 був невдалим."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Ваше членство у PRO закінчується через 1 день."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Ваше членство у PRO закінчується через %1 дні(в)."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Загальний доступ до %1 (%2) було призупинено."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Раніше призупинене %1 (%2) було відновлено."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Сьогодні"; \ No newline at end of file diff --git a/iMEGA/Languages/vi.lproj/Localizable.strings b/iMEGA/Languages/vi.lproj/Localizable.strings index 12bcfc269f..a1b0e9dc68 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.strings +++ b/iMEGA/Languages/vi.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="Bạn không thể tải về một thư mục không chứa dữ liệu"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="Tài khoản của bạn vừa được kích hoạt. Vui lòng đăng nhập."; +"accountAlreadyConfirmed"="Tài khoản đã được kích hoạt rồi. Xin đăng nhập."; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="Đường dẫn liên kết không hợp lệ"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="Nhập một email hợp lệ"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="Email bạn vừa điền không đúng kiểu"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="Xin nhập một mật khẩu hợp lệ"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="Email"; @@ -130,10 +130,8 @@ "passwordStrong"="Mật khẩu này chịu được tốt việc tấn công thử dò mật khẩu (brute-force) tinh vi. Hãy chắc rằng bạn ghi nhớ được mật khẩu này."; /* */ "agreeWithTheMEGATermsOfService"="Tôi đồng ý với Điều Khoản Dịch Vụ của MEGA"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Xin nhập một tên hợp lệ"; -/* */ -"invalidFirstNameAndLastName"="Tên và/hoặc Họ không phù hợp."; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="Hai mật khẩu này không trùng khớp"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="Đang chờ việc xác nhận email."; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="Xin nhập mật khẩu để xác thực tài khoản"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="Xác nhận tài khoản"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="Xác nhận tài khoản"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="Sai mật khẩu"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -715,7 +713,7 @@ /* "Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section" */ "onMEGA"="Trên mạng MEGA"; /* Button text on the Rubbish Bin page. This button will empty all files and folders currently stored in the rubbish bin. */ -"emptyRubbishBin"="Clear Rubbish Bin"; +"emptyRubbishBin"="Dọn Thùng Rác"; /* Alert title shown when you tap 'Empty Rubbish Bin' */ "emptyRubbishBinAlertTitle"="Tất cả các mục trong thùng rác sẽ bị xóa"; /* Text next to a switch that allows disabling the HTTP protocol for transfers */ @@ -846,7 +844,7 @@ /* Menu item */ "help"="Thắc Mắc Cần Trợ Giúp"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="Thư viện cách sử dụng & vấn đề"; +"helpCentreLabel"="Thư Viện Hướng Dẫn"; /* Section title that links you to the webpage that let you join and test the beta versions */ "Join Beta"="Join Beta"; /* Title to rate the app */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="Nâng cấp bất cứ lúc nào ở mục Tài Khoản Của Tôi"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]Ổ Mây sắp đầy.[/S] [A]Nâng cấp lên[/A] hạng PRO ngay để nhận [S]tới 4TB (4096 GB)[/S] để lưu trữ dữ liệu."; +"cloudDriveIsAlmostFull"="[S]Ổ Mây sắp đầy.[/S] [A]Nâng cấp lên[/A] hạng PRO ngay để nhận [S]tới 8 TB (8192 GB)[/S] để lưu trữ dữ liệu."; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]Tài khoản đã hết không gian lưu trữ.[/S] [A]Nâng cấp ngay[/A] lên hạng PRO và nhận lên [S]đến 4 TB (4096 GB)[/S] không gian lưu trữ."; +"cloudDriveIsFull"="[S]Tài khoản đã hết không gian lưu trữ.[/S] [A]Nâng cấp ngay[/A] lên hạng PRO và nhận lên [S]đến 8 TB (8192 GB)[/S] không gian lưu trữ."; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="Lưu trữ"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Bắt Đầu Cài Đặt"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Xin nhập một đoạn mã dài 6 chữ số lấy từ app Lập Xác Thực."; +"pleaseEnterTheSixDigitCode"="Xin nhập một đoạn mã dài 6 chữ số lấy từ App Lập Xác Thực."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Tại sao cần sử dụng chức năng Xác Thực 2-Bước?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Chức năng Xác Minh 2-Bước là một lớp bảo mật thứ nhì cho tài khoản. Nghĩa là trong trường hợp có người biết mật khẩu để lén vào tài khoản sẽ không thể vào được, và buộc phải nhập thêm đoạn mã 6 chữ số mà chỉ chủ sở hữu tài khoản mới có."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Cần phải có cài app lập xác thực cho thiết bị này"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Xác Minh 2-Bước được Bật"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Vào những lần đăng nhập tiếp theo, hệ thống sẽ yêu cầu nhập một đoạn mã dài 6 chữ số lấy từ app lập xác thực."; +"twoFactorAuthenticationEnabledDescription"="Vào những lần đăng nhập tiếp theo, hệ thống sẽ yêu cầu nhập một đoạn mã dài 6 chữ số lấy từ App Lập Xác Thực."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Xin lưu lại Mã Khoá Phục Hồi ở nơi an toàn"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Xác Minh 2-Bước Đã Tắt"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Quét hay sao chép đoạn chủng tử nằm dưới đây vào app Lập Xác Thực muốn sử dụng. Cũng nên lưu lại mã này ở nơi an toàn trong trường hợp mất điện thoại."; +"scanOrCopyTheSeed"="Quét hay sao chép đoạn chủng tử nằm dưới đây vào app Lập Xác Thực muốn sử dụng. Cũng nên lưu lại mã này ở nơi an toàn trong trường hợp bị mất máy."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Thiết bị có cài app Lập Xác Thực đã bị mất?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="Quản Lý Tệp Tin"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="Tất cả các tệp tin đều được giữ nguyên. Chỉ có các nội dung ghi lại phiên bản lịch sử tệp tin mới bị xoá."; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="Lên lịch dọn dẹp Thùng Rác:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="Dọn dữ liệu tồn đọng hơn"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="Thùng Rác tự động được dọn dẹp. Thời gian chờ cho mỗi việc dọn dẹp là 7 ngày (tối thiểu)."; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Thao tác này sẽ đăng xuất tài khoản ra khỏi tất cả các thiết bị đang sử dụng, ngoại trừ phiên hiện hành. Có chắc muốn tiến hành?"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="Thao tác này sẽ đăng xuất tài khoản ra khỏi tất cả các thiết bị đang sử dụng, ngoại trừ phiên hiện hành. Có chắc muốn tiến hành?"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="Xem dạng danh sách"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="Hiện dạng ảnh nhỏ"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="Xem dạng danh sách"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="Hiện dạng ảnh nhỏ"; +/* There are no notifications to display. */ +"No notifications"="Không có thông báo mới"; +/* The header of a notification related to payments */ +"Payment info"="Thông tin thanh toán"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="Gói thành viện hạng PRO sắp hết hạn"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="Thông báo yêu cầu gỡ bỏ"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="Mục bị gỡ bỏ đã được phục hồi"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="Vừa gửi bạn lời mời thêm vào danh bạ"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="Đã hủy yêu cầu danh bạ"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="Nhắc nhở: Bạn có một yêu cầu danh bạ"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="Đã xóa tên của bạn khỏi danh bạ"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="Hai bạn có tên danh bạ của nhau"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="Tài khoản đã bị xóa/vô hiệu hóa"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="Đã chặn liên lạc từ phía bạn"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="Đã bỏ qua yêu cầu danh bạ"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="Bạn vừa chấp nhận yêu cầu liên lạc này"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="Bạn vừa từ chối yêu cầu liên lạc này"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="Đã chấp nhận lời mời danh bạ từ bạn"; +/* When somebody denied your contact request */ +"Denied your contact request"="Đã từ chối lời mời danh bạ từ bạn"; +/* notification text */ +"A user has left the shared folder {0}"="Có nhân viên rời khỏi thư mục chia sẻ {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="Quyền truy cập tới thư mục đã bị xóa."; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="Đã loại bỏ [X] mục khỏi việc chia sẻ"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="Tiền bạn gửi để chi trả cho gói %1 đã được nhận."; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="Quá trình thanh toán cho gói %1 không thành công."; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="Gói thành viên hạng PRO của bạn sẽ hết hạn trong 1 ngày."; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="Tài khoản hạng PRO sẽ hết hạn trong %1 ngày tới."; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="Một %1 của bạn (tên %2) đã bị tháo bỏ do vi phạm bản quyền."; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="Nội dung %1 (tên %2) đã được phục hồi lại."; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="Hôm nay"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings index ab308d055e..67e9e8ff4d 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="您不能下载空的文件夹。"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="账号已可使用。请登入。"; +"accountAlreadyConfirmed"="您的账号已被激活。请登录。"; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="无效的链接"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="请输入有效的邮箱地址"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="email格式无效"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="请输入有效的密码"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="邮箱地址"; @@ -130,10 +130,8 @@ "passwordStrong"="这个密码将可承受最典型的暴力破解攻击。请确保您会好好记牢。"; /* */ "agreeWithTheMEGATermsOfService"="我同意MEGA的服务条款"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="请输入有效的名字"; -/* */ -"invalidFirstNameAndLastName"="无效的名或姓。"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="密码不匹配"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="正在等待确认电子信箱。"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="请输入您的密码来确认账户"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="确认您的账号"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="确认账户"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="错误密码"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -397,7 +395,7 @@ /* Message shown when there is an active link that can be removed or disabled */ "removeLink"="删除链接"; /* Message shown when there are some active links that can be removed or disabled */ -"removeLinks"="删除共享链接"; +"removeLinks"="删除链接"; /* Message shown when the link to a file or folder has been removed */ "linkRemoved"="链接已删除"; /* Message shown when the links to files and/or folders have been removed */ @@ -846,9 +844,9 @@ /* Menu item */ "help"="客户服务"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="协助中心"; +"helpCentreLabel"="帮助中心"; /* Section title that links you to the webpage that let you join and test the beta versions */ -"Join Beta"="Join Beta"; +"Join Beta"="加入测试版"; /* Title to rate the app */ "rateUsLabel"="请帮我们评分!"; /* Title of the Transfers section */ @@ -984,9 +982,9 @@ /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ "youCanUpgradeLaterInMyAccount"="您稍后可在\"我的帐户\"中进行升级"; /* Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are. */ -"cloudDriveIsAlmostFull"="[S]云盘快要满了。 [/S] [A]立即付费升级[/A]为PRO专业级帐户,并获得[S]最高4TB (4096 GB) [/S]的云端储存空间。"; +"cloudDriveIsAlmostFull"="[S]您的云盘几乎已满。[/S] [A]立即升级[/A]至PRO帐户,可获得[S]最高8TB (8192GB)[/S]的云储存空间。"; /* A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text. */ -"cloudDriveIsFull"="[S]您的帐号空间已满。 [/S] [A]立即升级[/A]为PRO专业版帐户,并可获得[S]最高4TB (4096 GB)[/S] 的云端储存空间。"; +"cloudDriveIsFull"="[S]您的帐户空间已满。[/S] [A]立即升级[/A]至PRO帐户,可获得[S]最高8TB (8192GB)[/S]的云储存空间。"; /* Storage related with the MEGA PRO account level you can subscribe */ "productSpace"="储存空间"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="开始安装"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="请输入您的验证程序中生成的6位验证码。"; +"pleaseEnterTheSixDigitCode"="请输入验证程序中生成的6位验证码。"; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="为什么您需要双重验证?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="双重验证是您账户的第二层安全保护。即使他人得知您的密码,无6位验证码也无权对其访问,因此只有您拥有访问权限。"; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="您需要在此设备上安装双重验证应用程序"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="您需要一个验证应用程序才能在MEGA上启用双重验证。 您可以为您的手机或平板电脑下载并安装Google验证,Duo Mobile,Authy或Microsoft验证程序。"; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="双重验证已启用"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="在您下次登录账户时,系统会要求您输入由验证程序提供的6位验证码。"; +"twoFactorAuthenticationEnabledDescription"="您在下次登录账户时,系统会要求您输入验证程序中提供的6位验证码。"; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="请将您的恢复秘钥保存在安全位置"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1590,17 +1590,17 @@ "scanOrCopyTheSeed"="您可以将种子扫描或复制到您的验证程序中。 以防您的手机丢失,请确保种子已备份至安全位置。"; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="丢失了您的验证设备?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="保存照片至资料库"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="保存视频至资料库"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="保存图片至照片"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="保存视频至照片"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="已下载的照片与/或视频将储存在您设备的媒体资料库中,而非离线部分。"; /* Setting associated with the 'Camera' of the device */ "Camera"="相机"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="保存至资料库"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="保存至照片"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="将MEGA应用程序中拍摄的照片与视频的副本保存在您设备的媒体资料库中。"; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="您持有这把密钥"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="允许访问照片"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="要分享照片与视频,请允许MEGA访问您的照片"; +"Please give the MEGA App permission to access Photos to share photos and videos."="请允许MEGA应用程序访问您的照片,此操作用于共享照片与视频。"; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="启用麦克风和相机"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="我们将向您发送通知,以便您立即在设备上收到新消息。"; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="开启访问"; +"Allow Access"="允许访问"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="文件管理"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="当前所有文件将被保留。仅删除文件的历史版本。"; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="您将删除所有文件的历史版本。 联系人与您共享的任何文件版本都需要由他们自行删除。\n \n请注意,当前文件不会被删除。"; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="垃圾箱清理:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="删除较旧的文件,早于"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="垃圾箱可以自动清洁。最短期限为7天。"; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="想要禁用垃圾箱清空计划或设置延长保留期,您需要订阅PRO计划。"; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="是否关闭所有其他会话?这会将您注销到除当前会话之外的所有其他活动会话。"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="是否关闭所有其他会话?这会将您注销到除当前会话之外的所有其他活动会话。"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="保存至照片"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="正在保存至照片……"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="已保存至照片"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="无法保存该项目"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="列表视图"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="缩略视图"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@已加入通话。"; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@已离开通话。"; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="网络连接不佳。"; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="您有一个活跃的群组通话。点击加入。"; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="触摸即可返回通话%@"; +/* Menu item to change from grid view to list view */ +"List view"="列表视图"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="缩略视图"; +/* There are no notifications to display. */ +"No notifications"="没有新通知"; +/* The header of a notification related to payments */ +"Payment info"="付款资讯"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="专业版会员计划即将到期"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="删除通知"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="移除已恢复"; +/* Label shown inside an unseen notification */ +"New"="新消息"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="向您发送了联系人邀请"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="联系人邀请已被删除"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="提醒: 您有一个联系人邀请"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="联系人将您删除"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="联系人关系已建立"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="账户已被删除/ 停用"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="屏蔽了您"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="您已忽略一个联系人邀请"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="您已接受一个联系人邀请"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="您已拒绝一个联系人邀请"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="已接受您的联系人邀请"; +/* When somebody denied your contact request */ +"Denied your contact request"="拒绝了您的联系人邀请"; +/* notification text */ +"A user has left the shared folder {0}"="一位用户已离开共享文件夹{0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="存取文件夹已移除"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="已添加1个文件"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="已添加%lld文件"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="已添加1个文件夹"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="已添加%lld文件夹"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="已添加1个文件和1个文件夹"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="已添加%lld文件和1个文件夹"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="已添加1个文件和%lld文件夹"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="已添加[A]文件和[B]文件夹"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="已删除[X]个共享项目"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="您的%1付款已经收到。"; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="您的%1付款不成功。"; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="您的专业版会员计划将在一天内到期"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="您的专业版会员计划将在%1天内到期"; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="您公开分享的%1 (%2) 已被移除。"; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="您%1 (%2) 的删除已恢复。"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="今天"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings index ce5ed231c2..9906dbf4f2 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings @@ -37,7 +37,7 @@ /* Alert message shown when the user tries to download an empty folder */ "emptyFolderMessage"="您不能下載空的資料夾。"; /* Message shown when the user clicks on a confirm account link that has already been used */ -"accountAlreadyConfirmed"="帳號已可使用。請登入。"; +"accountAlreadyConfirmed"="您的帳戶已通過認證。請登入帳戶以開始使用。"; /* Message shown when the user clicks on an link that is not valid */ "linkNotValid"="無效的連結"; /* Alert title shown when you tap on a encrypted file/folder link that can't be opened because it doesn't include the key to see its contents */ @@ -102,7 +102,7 @@ "emailInvalidFormat"="請輸入有效的郵箱地址"; /* Add contacts and share dialog error message when user try to add wrong email address */ "theEmailAddressFormatIsInvalid"="email格式無效"; -/* Message shown when the user enters a wrong password */ +/* Message shown when the user enters a wrong password */ "passwordInvalidFormat"="請輸入有效的密碼"; /* Hint text to suggest that the user has to write his email */ "emailPlaceholder"="郵箱地址"; @@ -130,10 +130,8 @@ "passwordStrong"="這個密碼將可承受最典型的暴力破解攻擊。請確保您會好好記牢。"; /* */ "agreeWithTheMEGATermsOfService"="我同意MEGA的服務條款"; -/* Error text shown when you have not entered a correct name */ +/* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="請輸入有效的名字"; -/* */ -"invalidFirstNameAndLastName"="無效的名或姓。"; /* Error text shown when you have not written the same password */ "passwordsDoNotMatch"="密碼不匹配"; /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ @@ -144,8 +142,8 @@ "awaitingEmailConfirmation"="正在等待確認電子信箱。"; /* Text shown on the confirm account view to remind the user what to do */ "confirmText"="請輸入您的密碼來確認賬戶"; -/* Button title that triggers the confirm account action */ -"confirmAccountButton"="確認您的帳號"; +/* Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). */ +"Confirm account"="確認帳戶"; /* Error text shown when you introduce a wrong password on the confirmation proccess */ "passwordWrong"="錯誤密碼"; /* Warning title shown when you try to confirm an account but you are logged in with another one */ @@ -846,7 +844,7 @@ /* Menu item */ "help"="支援"; /* Title of the section to access MEGA's help centre */ -"helpCentreLabel"="協助中心"; +"helpCentreLabel"="幫助中心"; /* Section title that links you to the webpage that let you join and test the beta versions */ "Join Beta"="Join Beta"; /* Title to rate the app */ @@ -1567,7 +1565,7 @@ /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ "beginSetup"="Begin Setup"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ -"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authentication app."; +"pleaseEnterTheSixDigitCode"="Please enter the 6-digit code generated by your Authenticator App."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ "whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ @@ -1576,10 +1574,12 @@ "whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +/* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ +"You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="已啟用兩階段驗證"; /* A message on the dialog shown after 2FA was successfully enabled. */ -"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your authentication app."; +"twoFactorAuthenticationEnabledDescription"="Next time you login to your account you will be asked to enter a 6-digit code provided by your Authenticator App."; /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Please save your Recovery Key in a safe location"; /* An informational message on the Backup Recovery Key dialog. */ @@ -1587,20 +1587,20 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="已停用兩階段驗證"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your phone."; +"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Lost your Authenticator device?"; -/* Settings section title where you can enable the option to 'Save Images in Library' */ -"Save Images in Library"="Save Images in Library"; -/* Settings section title where you can enable the option to 'Save Videos in Library' */ -"Save Videos in Library"="Save Videos in Library"; +/* Settings section title where you can enable the option to 'Save Images in Photos' */ +"Save Images in Photos"="Save Images in Photos"; +/* Settings section title where you can enable the option to 'Save Videos in Photos' */ +"Save Videos in Photos"="Save Videos in Photos"; /* Footer text shown under the settings for download options 'Save Images/Videos in Library' */ "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."="Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section."; /* Setting associated with the 'Camera' of the device */ "Camera"="Camera"; -/* Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app */ -"Save in Library"="Save in Library"; -/* Footer text shown under the Camera setting to explain the option 'Save in Library' */ +/* Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app */ +"Save in Photos"="Save in Photos"; +/* Footer text shown under the Camera setting to explain the option 'Save in Photos' */ "Save a copy of the images and videos taken from the MEGA app in your device’s media library."="Save a copy of the images and videos taken from the MEGA app in your device’s media library."; /* Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys */ "You hold the keys"="You hold the keys"; @@ -1625,7 +1625,7 @@ /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ -"To share photos and videos, allow MEGA to access your photos"="To share photos and videos, allow MEGA to access your photos"; +"Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ @@ -1635,7 +1635,7 @@ /* Detailed explanation of why the user should give permission to deliver notifications */ "We would like to send you notifications so you receive new messages on your device instantly."="We would like to send you notifications so you receive new messages on your device instantly."; /* Button which triggers a request for a specific permission, that have been explained to the user beforehand */ -"Enable Access"="Enable Access"; +"Allow Access"="Allow Access"; /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="檔案管理"; /* Title of the option to enable or disable file versioning on Settings section */ @@ -1656,8 +1656,8 @@ "All current files will remain. Only historic versions of your files will be deleted."="此操作將永久刪除所有您自己的檔案版本。您的聯絡人分享給您的檔案版本均只能由該聯絡人刪除。"; /* Text of the dialog to delete all the file versions of the account */ "You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; -/* */ -"Rubbish-Bin Cleaning Scheduler"="Rubbish-Bin Cleaning Scheduler"; +/* Title for the Rubbish-Bin Cleaning Scheduler feature */ +"Rubbish-Bin Cleaning Scheduler:"="垃圾夾清理調度:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ "Remove files older than"="刪除較舊的檔案,早於"; /* Label for any ‘Days’ button, link, text, title, etc. - (String as short as possible). */ @@ -1667,6 +1667,114 @@ /* New server-side rubbish-bin cleaning scheduler description (for PRO users) */ "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days."="系統可以定期為您清理垃圾桶。可設定的最短保留期限為7天。"; /* Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user */ -"To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"="To disable Rubbish-Bin Cleaning Scheduler or set a longer retention period you need to subscribe to a PRO plan"; +"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."="To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan."; /* Confirmation dialog for the button that logs the user out of all sessions except the current one. */ -"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="您想關閉所有其他頁面嗎?這將使您登出除了目前頁面之外的所有其他活動頁面。"; \ No newline at end of file +"Do you want to close all other sessions? This will log you out on all other active sessions except the current one."="您想關閉所有其他頁面嗎?這將使您登出除了目前頁面之外的所有其他活動頁面。"; +/* A button label which allows the users save images/videos in the Photos app. */ +"Save to Photos"="Save to Photos"; +/* Text shown when starting the process to save a photo or video to Photos app */ +"Saving to Photos…"="Saving to Photos…"; +/* Text shown when a photo or video is saved to Photos app */ +"Saved to Photos"="Saved to Photos"; +/* Text shown when an error occurs when trying to save a photo or video to Photos app */ +"Could not save Item"="Could not save Item"; +/* Text shown for switching from thumbnail view to list view. */ +"List view"="顯示列表"; +/* Text shown for switching from list view to thumbnail view. */ +"Thumbnail view"="縮圖檢視"; +/* Message to inform the local user that someone has joined the current group call */ +"%@ joined the call."="%@ joined the call."; +/* Message to inform the local user that someone has left the current group call */ +"%@ left the call."="%@ left the call."; +/* Message to inform the local user is having a bad quality network with someone in the current group call */ +"Poor connection."="Poor connection."; +/* Message shown in a chat room when there is an active group call */ +"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +/* Message shown in a chat room for a group call in progress displaying the duration of the call */ +"Touch to return to call %@"="Touch to return to call %@"; +/* Menu item to change from grid view to list view */ +"List view"="顯示列表"; +/* Menu item to change from list view to grid view */ +"Thumbnail view"="縮圖檢視"; +/* There are no notifications to display. */ +"No notifications"="沒有新通知"; +/* The header of a notification related to payments */ +"Payment info"="付款資訊"; +/* A title for a notification saying the user’s pricing plan will expire soon. */ +"PRO membership plan expiring soon"="專業版會員專案即將到期"; +/* The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. */ +"Takedown notice"="刪除通知"; +/* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ +"Takedown reinstated"="移除已復原"; +/* Label shown inside an unseen notification */ +"New"="New"; +/* When a contact sent a contact/friend request */ +"Sent you a contact request"="向您發送了聯絡人邀請"; +/* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ +"Cancelled their contact request"="聯絡人邀請已被刪除"; +/* A reminder notification to remind the user to respond to the contact request. */ +"Reminder: You have a contact request"="提醒: 您有一個聯絡人邀請"; +/* A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. */ +"Deleted you as a contact"="聯繫人將您刪除"; +/* A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books). */ +"Contact relationship established"="聯絡人關係已建立"; +/* A notification telling the user that one of their contact’s accounts has been deleted or deactivated. */ +"Account has been deleted/deactivated"="賬戶已被刪除/ 停用"; +/* A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact. */ +"Blocked you as a contact"="屏蔽了您"; +/* Response text after clicking Ignore on an incoming contact request notification. */ +"You ignored a contact request"="您已忽略一個聯絡人邀請"; +/* Response text after clicking Accept on an incoming contact request notification. */ +"You accepted a contact request"="您已接受一個聯絡人邀請"; +/* Response text after clicking Deny on an incoming contact request notification. */ +"You denied a contact request"="您已拒絕一個聯絡人邀請"; +/* When somebody accepted your contact request */ +"Accepted your contact request"="已接受您的聯絡人邀請"; +/* When somebody denied your contact request */ +"Denied your contact request"="拒絕了您的聯絡人邀請"; +/* notification text */ +"A user has left the shared folder {0}"="A user has left the shared folder {0}"; +/* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ +"Access to folders was removed."="存取資料夾已移除"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file"="Added 1 file"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files"="Added %lld files"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 folder"="Added 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld folders"="Added %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +/* Content of a notification that informs how many files and folders have been added to a shared folder */ +"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ +"Removed [X] items from a share"="已從共享中移除[X]項目"; +/* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ +"Your payment for the %1 plan was received."="您的 %1付款已經收到。"; +/* A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II. */ +"Your payment for the %1 plan was unsuccessful."="您的 %1付款不成功。"; +/* The professional pricing plan which the user is currently on will expire in one day. */ +"Your PRO membership plan will expire in 1 day."="您的專業版會員專案將在一天內到期"; +/* The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed. */ +"Your PRO membership plan will expire in %1 days."="您的專業版會員專案將在%1天內到期"; +/* The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your publicly shared %1 (%2) has been taken down."="您公開分享的 %1 (%2) 已被移除。"; +/* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ +"Your taken down %1 (%2) has been reinstated."="您 %1 (%2) 之刪除已復原。"; +/* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ +"Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."="Allow your contacts to see the last time you were active on MEGA. If disabled you won’t be able to see the activity status of your contacts."; +/* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ +"Show"="Show"; +/* Shown when viewing a 1on1 chat (at least for now), if the user is offline. */ +"Last seen %s"="Last seen %s"; +/* Text to inform the user the 'Last seen' time of a contact, for example 'Last seen 20 Nov 18 15:15'. String as sort as possible. */ +"Last seen [A] [B]"="Last seen [A] [B]"; +/* Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) */ +"Last seen a long time ago"="Last seen a long time ago"; +/* */ +"Today"="今天"; \ No newline at end of file diff --git a/iMEGA/Links/FileLinkViewController.h b/iMEGA/Links/FileLinkViewController.h index 815f71f6e6..9d86f56bf9 100644 --- a/iMEGA/Links/FileLinkViewController.h +++ b/iMEGA/Links/FileLinkViewController.h @@ -2,7 +2,8 @@ @interface FileLinkViewController : UIViewController -@property (nonatomic, strong) NSString *fileLinkString; +@property (nonatomic, strong) NSString *publicLinkString; +@property (nonatomic, strong) NSString *linkEncryptedString; @property (nonatomic, strong) MEGARequest *request; @property (nonatomic, strong) MEGAError *error; diff --git a/iMEGA/Links/FileLinkViewController.m b/iMEGA/Links/FileLinkViewController.m index 47602ce729..b28c89600a 100644 --- a/iMEGA/Links/FileLinkViewController.m +++ b/iMEGA/Links/FileLinkViewController.m @@ -5,15 +5,17 @@ #import "Helper.h" #import "MEGAGetPublicNodeRequestDelegate.h" #import "MEGANode+MNZCategory.h" +#import "MEGALinkManager.h" +#import "MEGAPhotoBrowserViewController.h" #import "MEGASdkManager.h" #import "MEGAReachabilityManager.h" #import "NSString+MNZCategory.h" #import "UIApplication+MNZCategory.h" #import "UIImageView+MNZCategory.h" -#import "UnavailableLinkView.h" +#import "UITextField+MNZCategory.h" #import "CustomActionViewController.h" -#import "MEGAPhotoBrowserViewController.h" +#import "UnavailableLinkView.h" @interface FileLinkViewController () @@ -136,9 +138,9 @@ - (void)processRequestResult { if (self.node.name.mnz_isImagePathExtension || self.node.name.mnz_isVideoPathExtension) { [self dismissViewControllerAnimated:YES completion:^{ MEGAPhotoBrowserViewController *photoBrowserVC = [MEGAPhotoBrowserViewController photoBrowserWithMediaNodes:@[self.node].mutableCopy api:[MEGASdkManager sharedMEGASdk] displayMode:DisplayModeFileLink presentingNode:self.node preferredIndex:0]; - photoBrowserVC.publicLink = self.fileLinkString; + photoBrowserVC.publicLink = self.publicLinkString; - [UIApplication.mnz_visibleViewController presentViewController:photoBrowserVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:photoBrowserVC animated:YES completion:nil]; }]; } else { [self setNodeInfo]; @@ -194,6 +196,9 @@ - (void)showDecryptionAlert { [decryptionAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = AMLocalizedString(@"decryptionKey", @"Hint text to suggest that the user has to write the decryption key"); [textField addTarget:self action:@selector(decryptionAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + return !textField.text.mnz_isEmpty; + }; }]; [decryptionAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { @@ -203,7 +208,7 @@ - (void)showDecryptionAlert { UIAlertAction *decryptAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"decrypt", @"Button title to try to decrypt the link") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { NSString *key = decryptionAlertController.textFields.firstObject.text; - NSString *linkString = ([[key substringToIndex:1] isEqualToString:@"!"]) ? self.fileLinkString : [self.fileLinkString stringByAppendingString:@"!"]; + NSString *linkString = ([[key substringToIndex:1] isEqualToString:@"!"]) ? self.publicLinkString : [self.publicLinkString stringByAppendingString:@"!"]; linkString = [linkString stringByAppendingString:key]; MEGAGetPublicNodeRequestDelegate *delegate = [[MEGAGetPublicNodeRequestDelegate alloc] initWithCompletion:^(MEGARequest *request, MEGAError *error) { @@ -211,6 +216,7 @@ - (void)showDecryptionAlert { self.error = error; [self processRequestResult]; }]; + delegate.savePublicHandle = YES; [SVProgressHUD show]; [[MEGASdkManager sharedMEGASdk] publicNodeForMegaFileLink:linkString delegate:delegate]; @@ -250,25 +256,18 @@ - (void)setNodeInfo { [self setUIItemsHidden:NO]; } -- (void)decryptionAlertTextFieldDidChange:(UITextField *)sender { +- (void)decryptionAlertTextFieldDidChange:(UITextField *)textField { UIAlertController *decryptionAlertController = (UIAlertController *)self.presentedViewController; if (decryptionAlertController) { - UITextField *textField = decryptionAlertController.textFields.firstObject; UIAlertAction *rightButtonAction = decryptionAlertController.actions.lastObject; - BOOL enableRightButton = NO; - if (textField.text.length > 0) { - enableRightButton = YES; - } - rightButtonAction.enabled = enableRightButton; + rightButtonAction.enabled = !textField.text.mnz_isEmpty; } } #pragma mark - IBActions - (IBAction)cancelTouchUpInside:(UIBarButtonItem *)sender { - - [Helper setLinkNode:nil]; - [Helper setSelectedOptionOnLink:0]; + [MEGALinkManager resetUtilsForLinksWithoutSession]; [SVProgressHUD dismiss]; @@ -335,12 +334,17 @@ - (void)performAction:(MegaNodeActionType)action inNode:(MEGANode *)node fromSen break; case MegaNodeActionTypeShare: { - UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.fileLinkString] applicationActivities:nil]; + NSString *link = self.linkEncryptedString ? self.linkEncryptedString : self.publicLinkString; + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[link] applicationActivities:nil]; activityVC.popoverPresentationController.barButtonItem = sender; [self presentViewController:activityVC animated:YES completion:nil]; break; } + case MegaNodeActionTypeSaveToPhotos: + [node mnz_saveToPhotosWithApi:[MEGASdkManager sharedMEGASdk]]; + break; + default: break; } diff --git a/iMEGA/Links/FolderLinkViewController.h b/iMEGA/Links/FolderLinkViewController.h index d93b291a8d..a7aab0cede 100644 --- a/iMEGA/Links/FolderLinkViewController.h +++ b/iMEGA/Links/FolderLinkViewController.h @@ -3,6 +3,7 @@ @interface FolderLinkViewController : UIViewController @property (nonatomic) BOOL isFolderRootNode; -@property (nonatomic, strong) NSString *folderLinkString; +@property (nonatomic, strong) NSString *publicLinkString; +@property (nonatomic, strong) NSString *linkEncryptedString; @end diff --git a/iMEGA/Links/FolderLinkViewController.m b/iMEGA/Links/FolderLinkViewController.m index bf8ec71fcf..70e31df691 100644 --- a/iMEGA/Links/FolderLinkViewController.m +++ b/iMEGA/Links/FolderLinkViewController.m @@ -8,21 +8,26 @@ #import "DisplayMode.h" #import "Helper.h" +#import "MEGANavigationController.h" #import "MEGANode+MNZCategory.h" #import "MEGANodeList+MNZCategory.h" +#import "MEGAPhotoBrowserViewController.h" #import "MEGAReachabilityManager.h" #import "MEGASdkManager.h" -#import "NodeTableViewCell.h" + #import "NSString+MNZCategory.h" +#import "MEGALinkManager.h" +#import "UIApplication+MNZCategory.h" #import "UIImageView+MNZCategory.h" -#import "UnavailableLinkView.h" +#import "UITextField+MNZCategory.h" #import "BrowserViewController.h" #import "CustomActionViewController.h" +#import "NodeTableViewCell.h" #import "MainTabBarController.h" -#import "MEGANavigationController.h" -#import "MEGAPhotoBrowserViewController.h" -#import "MyAccountHallViewController.h" +#import "LoginViewController.h" +#import "LinkOption.h" +#import "UnavailableLinkView.h" @interface FolderLinkViewController () { @@ -108,8 +113,7 @@ - (void)viewDidLoad { self.closeBarButtonItem.title = AMLocalizedString(@"close", @"A button label."); if (self.isFolderRootNode) { - [MEGASdkManager sharedMEGASdkFolder]; - [[MEGASdkManager sharedMEGASdkFolder] loginToFolderLink:self.folderLinkString delegate:self]; + [[MEGASdkManager sharedMEGASdkFolder] loginToFolderLink:self.publicLinkString delegate:self]; self.navigationItem.leftBarButtonItem = self.closeBarButtonItem; @@ -269,6 +273,9 @@ - (void)showDecryptionAlert { [decryptionAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = AMLocalizedString(@"decryptionKey", nil); [textField addTarget:self action:@selector(decryptionTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { + return !textField.text.mnz_isEmpty; + }; }]; [decryptionAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { @@ -281,9 +288,9 @@ - (void)showDecryptionAlert { NSString *linkString; NSString *key = decryptionAlertController.textFields.firstObject.text; if ([[key substringToIndex:1] isEqualToString:@"!"]) { - linkString = self.folderLinkString; + linkString = self.publicLinkString; } else { - linkString = [self.folderLinkString stringByAppendingString:@"!"]; + linkString = [self.publicLinkString stringByAppendingString:@"!"]; } linkString = [linkString stringByAppendingString:key]; @@ -325,7 +332,7 @@ - (void)navigateToNodeWithBase64Handle:(NSString *)base64Handle { FolderLinkViewController *folderLinkVC = [storyboard instantiateViewControllerWithIdentifier:@"FolderLinkViewControllerID"]; [folderLinkVC setParentNode:node]; [folderLinkVC setIsFolderRootNode:NO]; - [folderLinkVC setFolderLinkString:self.folderLinkString]; + folderLinkVC.publicLinkString = self.publicLinkString; [self.navigationController pushViewController:folderLinkVC animated:NO]; } else { @@ -340,12 +347,11 @@ - (void)navigateToNodeWithBase64Handle:(NSString *)base64Handle { } } -- (void)decryptionTextFieldDidChange:(UITextField *)sender { - if (self.presentedViewController) { - UIAlertController *decryptionAlertController = (UIAlertController *)self.presentedViewController; - UITextField *decryptionTextField = decryptionAlertController.textFields.firstObject; +- (void)decryptionTextFieldDidChange:(UITextField *)textField { + UIAlertController *decryptionAlertController = (UIAlertController *)self.presentedViewController; + if (decryptionAlertController) { UIAlertAction *okAction = decryptionAlertController.actions.lastObject; - okAction.enabled = decryptionTextField.text.length > 0; + okAction.enabled = !textField.text.mnz_isEmpty; } } @@ -362,8 +368,7 @@ - (void)presentMediaNode:(MEGANode *)node { #pragma mark - IBActions - (IBAction)cancelAction:(UIBarButtonItem *)sender { - [Helper setLinkNode:nil]; - [Helper setSelectedOptionOnLink:0]; + [MEGALinkManager resetUtilsForLinksWithoutSession]; [[MEGASdkManager sharedMEGASdkFolder] logout]; @@ -491,36 +496,31 @@ - (IBAction)downloadAction:(UIBarButtonItem *)sender { if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { [self dismissViewControllerAnimated:YES completion:^{ - if ([[[[[UIApplication sharedApplication] delegate] window] rootViewController] isKindOfClass:[MainTabBarController class]]) { - MainTabBarController *mainTBC = (MainTabBarController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; - mainTBC.selectedIndex = MYACCOUNT; - MEGANavigationController *navigationController = [mainTBC.childViewControllers objectAtIndex:MYACCOUNT]; - MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; - [myAccountHallVC openOffline]; + if ([UIApplication.sharedApplication.keyWindow.rootViewController isKindOfClass:MainTabBarController.class]) { + MainTabBarController *mainTBC = (MainTabBarController *)UIApplication.sharedApplication.keyWindow.rootViewController; + [mainTBC showOffline]; } [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", nil)]; if (self.selectedNodesArray.count != 0) { - for (MEGANode *node in _selectedNodesArray) { + for (MEGANode *node in self.selectedNodesArray) { [Helper downloadNode:node folderPath:[Helper relativePathForOffline] isFolderLink:YES shouldOverwrite:NO]; } } else { - [Helper downloadNode:_parentNode folderPath:[Helper relativePathForOffline] isFolderLink:YES shouldOverwrite:NO]; + [Helper downloadNode:self.parentNode folderPath:[Helper relativePathForOffline] isFolderLink:YES shouldOverwrite:NO]; } }]; } else { if (self.selectedNodesArray.count != 0) { - [[Helper nodesFromLinkMutableArray] addObjectsFromArray:_selectedNodesArray]; + [MEGALinkManager.nodesFromLinkMutableArray addObjectsFromArray:self.selectedNodesArray]; } else { - [[Helper nodesFromLinkMutableArray] addObject:_parentNode]; + [MEGALinkManager.nodesFromLinkMutableArray addObject:self.parentNode]; } - [Helper setSelectedOptionOnLink:4]; //Download folder or nodes from link + MEGALinkManager.selectedOption = LinkOptionDownloadFolderOrNodes; [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; } - - //TODO: Make a logout in sharedMEGASdkFolder after download the link or the selected nodes. } - (IBAction)importAction:(UIBarButtonItem *)sender { @@ -538,18 +538,18 @@ - (IBAction)importAction:(UIBarButtonItem *)sender { browserVC.selectedNodesArray = [NSArray arrayWithObject:self.parentNode]; } - [[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:navigationController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; }]; } else { if (self.selectedNodesArray.count != 0) { - [[Helper nodesFromLinkMutableArray] addObjectsFromArray:_selectedNodesArray]; + [MEGALinkManager.nodesFromLinkMutableArray addObjectsFromArray:self.selectedNodesArray]; } else { if (self.parentNode == nil) { return; } - [[Helper nodesFromLinkMutableArray] addObject:self.parentNode]; + [MEGALinkManager.nodesFromLinkMutableArray addObject:self.parentNode]; } - [Helper setSelectedOptionOnLink:3]; //Import folder or nodes from link + MEGALinkManager.selectedOption = LinkOptionImportFolderOrNodes; [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; } @@ -612,7 +612,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.nameLabel.text = node.name; - cell.nodeHandle = node.handle; + cell.node = node; if (tableView.isEditing) { for (MEGANode *n in _selectedNodesArray) { @@ -655,7 +655,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath FolderLinkViewController *folderLinkVC = [storyboard instantiateViewControllerWithIdentifier:@"FolderLinkViewControllerID"]; [folderLinkVC setParentNode:node]; [folderLinkVC setIsFolderRootNode:NO]; - [folderLinkVC setFolderLinkString:self.folderLinkString]; + folderLinkVC.publicLinkString = self.publicLinkString; [self.navigationController pushViewController:folderLinkVC animated:YES]; break; } @@ -899,9 +899,13 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } isFetchNodesDone = YES; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedLongLong:request.nodeHandle] forKey:@"kLastPublicHandleAccessed"]; + [[NSUserDefaults standardUserDefaults] setDouble:[NSDate date].timeIntervalSince1970 forKey:@"kLastPublicTimestampAccessed"]; + [self reloadUI]; - NSArray *componentsArray = [self.folderLinkString componentsSeparatedByString:@"!"]; + NSArray *componentsArray = [self.publicLinkString componentsSeparatedByString:@"!"]; if (componentsArray.count == 4) { [self navigateToNodeWithBase64Handle:componentsArray.lastObject]; } @@ -920,8 +924,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } case MEGARequestTypeGetAttrFile: { - for (NodeTableViewCell *nodeTableViewCell in [self.tableView visibleCells]) { - if (request.nodeHandle == nodeTableViewCell.nodeHandle) { + for (NodeTableViewCell *nodeTableViewCell in self.tableView.visibleCells) { + if (request.nodeHandle == nodeTableViewCell.node.handle) { MEGANode *node = [api nodeForHandle:request.nodeHandle]; [Helper setThumbnailForNode:node api:api cell:nodeTableViewCell reindexNode:NO]; } @@ -959,12 +963,17 @@ - (void)performAction:(MegaNodeActionType)action inNode:(MEGANode *)node fromSen } case MegaNodeActionTypeShare: { - UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.folderLinkString] applicationActivities:nil]; + NSString *link = self.linkEncryptedString ? self.linkEncryptedString : self.publicLinkString; + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[link] applicationActivities:nil]; activityVC.popoverPresentationController.barButtonItem = sender; [self presentViewController:activityVC animated:YES completion:nil]; break; } + case MegaNodeActionTypeSaveToPhotos: + [node mnz_saveToPhotosWithApi:[MEGASdkManager sharedMEGASdkFolder]]; + break; + default: break; } diff --git a/iMEGA/Login/CheckEmailAndFollowTheLinkViewController.m b/iMEGA/Login/CheckEmailAndFollowTheLinkViewController.m index 0d2450aca6..b535899056 100644 --- a/iMEGA/Login/CheckEmailAndFollowTheLinkViewController.m +++ b/iMEGA/Login/CheckEmailAndFollowTheLinkViewController.m @@ -6,25 +6,25 @@ #import "NSString+MNZCategory.h" +#import "InputView.h" #import "Helper.h" #import "MEGALoginRequestDelegate.h" #import "MEGASendSignupLinkRequestDelegate.h" #import "MEGAReachabilityManager.h" #import "MEGASdkManager.h" -@interface CheckEmailAndFollowTheLinkViewController () +@interface CheckEmailAndFollowTheLinkViewController () @property (weak, nonatomic) IBOutlet NSLayoutConstraint *mailImageTopLayoutConstraint; @property (weak, nonatomic) IBOutlet UIImageView *mailImageView; - -@property (weak, nonatomic) IBOutlet UILabel *awaitingEmailConfirmation; +@property (weak, nonatomic) IBOutlet UILabel *awaitingEmailConfirmationLabel; +@property (weak, nonatomic) IBOutlet UILabel *checkYourEmailLabel; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *checkYourEmailBottomLayoutConstraint; -@property (weak, nonatomic) IBOutlet UITextField *emailTextField; +@property (weak, nonatomic) IBOutlet InputView *emailInputView; +@property (weak, nonatomic) IBOutlet UILabel *misspelledLabel; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *resendButtonTopLayoutConstraint; @property (weak, nonatomic) IBOutlet UIButton *resendButton; @property (weak, nonatomic) IBOutlet UIButton *cancelButton; -@property (weak, nonatomic) IBOutlet UILabel *checkYourEmailLabel; -@property (weak, nonatomic) IBOutlet UILabel *misspelledLabel; @property (nonatomic, copy) NSString *email; @property (nonatomic, copy) NSString *name; @@ -37,48 +37,62 @@ @implementation CheckEmailAndFollowTheLinkViewController - (void)viewDidLoad { [super viewDidLoad]; - if ([[UIDevice currentDevice] iPhone4X]) { - self.mailImageTopLayoutConstraint.constant = 24.f; - self.checkYourEmailBottomLayoutConstraint.constant = 59.f; + self.email = [SAMKeychain passwordForService:@"MEGA" account:@"email"]; + self.name = [SAMKeychain passwordForService:@"MEGA" account:@"name"]; + self.base64pwkey = [SAMKeychain passwordForService:@"MEGA" account:@"base64pwkey"]; + + if (UIDevice.currentDevice.iPhone4X) { + self.mailImageTopLayoutConstraint.constant = 24.0f; + self.checkYourEmailBottomLayoutConstraint.constant = 6.0f; self.resendButtonTopLayoutConstraint.constant = 20.0f; - } else if ([[UIDevice currentDevice] iPhone5X]) { - self.mailImageTopLayoutConstraint.constant = 24.f; + } else if (UIDevice.currentDevice.iPhone5X) { + self.mailImageTopLayoutConstraint.constant = 24.0f; } - self.awaitingEmailConfirmation.text = AMLocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); - [_emailTextField setPlaceholder:AMLocalizedString(@"emailPlaceholder", nil)]; - - _email = [SAMKeychain passwordForService:@"MEGA" account:@"email"]; - _name = [SAMKeychain passwordForService:@"MEGA" account:@"name"]; - _base64pwkey = [SAMKeychain passwordForService:@"MEGA" account:@"base64pwkey"]; - - _emailTextField.text = self.email; + self.awaitingEmailConfirmationLabel.text = AMLocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); + self.checkYourEmailLabel.text = AMLocalizedString(@"accountNotConfirmed", @"Text shown just after creating an account to remenber the user what to do to complete the account creation proccess"); + self.emailInputView.inputTextField.text = self.email; + self.misspelledLabel.text = AMLocalizedString(@"misspelledEmailAddress", @"A hint shown at the bottom of the Send Signup Link dialog to tell users they can edit the provided email."); + [self.resendButton setTitle:AMLocalizedString(@"resend", @"A button to resend the email confirmation.") forState:UIControlStateNormal]; + [self.cancelButton setTitle:AMLocalizedString(@"cancel", nil) forState:UIControlStateNormal]; self.resendButton.layer.borderColor = [UIColor mnz_gray999999].CGColor; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)]; [self.view addGestureRecognizer:tap]; + + self.emailInputView.inputTextField.delegate = self; + self.emailInputView.inputTextField.keyboardType = UIKeyboardTypeEmailAddress; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.checkYourEmailLabel.text = AMLocalizedString(@"accountNotConfirmed", @"Text shown just after creating an account to remenber the user what to do to complete the account creation proccess"); - self.misspelledLabel.text = AMLocalizedString(@"misspelledEmailAddress", @"A hint shown at the bottom of the Send Signup Link dialog to tell users they can edit the provided email."); - [self.resendButton setTitle:AMLocalizedString(@"resend", @"A button to resend the email confirmation.") forState:UIControlStateNormal]; - [self.cancelButton setTitle:AMLocalizedString(@"cancel", nil) forState:UIControlStateNormal]; [[MEGASdkManager sharedMEGASdk] addMEGAGlobalDelegate:self]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; + [[MEGASdkManager sharedMEGASdk] removeMEGAGlobalDelegate:self]; } #pragma mark - Private --(void)dismissKeyboard { - [self.emailTextField resignFirstResponder]; +- (void)dismissKeyboard { + [self.emailInputView.inputTextField resignFirstResponder]; +} + +- (void)setErrorState:(BOOL)error { + if (error) { + self.emailInputView.topLabel.text = AMLocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field"); + self.emailInputView.topLabel.textColor = UIColor.mnz_redError; + self.emailInputView.inputTextField.textColor = UIColor.mnz_redError; + } else { + self.emailInputView.topLabel.text = AMLocalizedString(@"emailPlaceholder", nil); + self.emailInputView.topLabel.textColor = UIColor.mnz_gray999999; + self.emailInputView.inputTextField.textColor = UIColor.blackColor; + } } #pragma mark - IBActions @@ -97,13 +111,13 @@ - (IBAction)cancelTouchUpInside:(UIButton *)sender { - (IBAction)resendTouchUpInside:(UIButton *)sender { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - if ([self.emailTextField.text mnz_isValidEmail]) { - [self.emailTextField resignFirstResponder]; + BOOL validEmail = [self.emailInputView.inputTextField.text mnz_isValidEmail]; + if (validEmail) { + [self.emailInputView.inputTextField resignFirstResponder]; MEGASendSignupLinkRequestDelegate *sendSignupLinkRequestDelegate = [[MEGASendSignupLinkRequestDelegate alloc] init]; - [[MEGASdkManager sharedMEGASdk] fastSendSignupLinkWithEmail:self.emailTextField.text base64pwkey:self.base64pwkey name:self.name delegate:sendSignupLinkRequestDelegate]; - } else { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field")]; + [[MEGASdkManager sharedMEGASdk] fastSendSignupLinkWithEmail:self.emailInputView.inputTextField.text base64pwkey:self.base64pwkey name:self.name delegate:sendSignupLinkRequestDelegate]; } + [self setErrorState:!validEmail]; } } @@ -111,7 +125,7 @@ - (IBAction)resendTouchUpInside:(UIButton *)sender { #pragma mark - UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { - [self.emailTextField resignFirstResponder]; + [self.emailInputView.inputTextField resignFirstResponder]; [self resendTouchUpInside:nil]; return YES; diff --git a/iMEGA/Login/ConfirmAccountViewController.h b/iMEGA/Login/ConfirmAccountViewController.h index c0b01a89d6..22afb03d2a 100644 --- a/iMEGA/Login/ConfirmAccountViewController.h +++ b/iMEGA/Login/ConfirmAccountViewController.h @@ -1,16 +1,10 @@ #import -#import "MEGASdkManager.h" +#import "URLType.h" @interface ConfirmAccountViewController : UIViewController -typedef NS_ENUM(NSUInteger, ConfirmType) { - ConfirmTypeAccount = 0, - ConfirmTypeEmail, - ConfirmTypeCancelAccount -}; - -@property (nonatomic) ConfirmType confirmType; +@property (nonatomic) URLType urlType; @property (strong, nonatomic) NSString *confirmationLinkString; @property (strong, nonatomic) NSString *emailString; diff --git a/iMEGA/Login/ConfirmAccountViewController.m b/iMEGA/Login/ConfirmAccountViewController.m index f918a86a14..1af062c913 100644 --- a/iMEGA/Login/ConfirmAccountViewController.m +++ b/iMEGA/Login/ConfirmAccountViewController.m @@ -4,29 +4,31 @@ #import "SVProgressHUD.h" #import "Helper.h" +#import "MEGALinkManager.h" #import "MEGALoginRequestDelegate.h" #import "MEGAReachabilityManager.h" -#import "PasswordView.h" +#import "MEGASdkManager.h" +#import "NSString+MNZCategory.h" #import "UIApplication+MNZCategory.h" +#import "InputView.h" +#import "PasswordView.h" + @interface ConfirmAccountViewController () -@property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *logoTopLayoutConstraint; +@property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *confirmTextTopLayoutConstraint; @property (weak, nonatomic) IBOutlet UILabel *confirmTextLabel; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *confirmTextBottomLayoutConstraint; -@property (weak, nonatomic) IBOutlet UITextField *emailTextField; +@property (weak, nonatomic) IBOutlet InputView *emailInputView; @property (weak, nonatomic) IBOutlet PasswordView *passwordView; @property (weak, nonatomic) IBOutlet UIButton *confirmAccountButton; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *confirmAccountButtonTopLayoutConstraint; @property (weak, nonatomic) IBOutlet UIButton *cancelButton; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *passwordViewHeightConstraint; - @end @implementation ConfirmAccountViewController @@ -36,38 +38,54 @@ @implementation ConfirmAccountViewController - (void)viewDidLoad { [super viewDidLoad]; - if ([[UIDevice currentDevice] iPhone4X]) { - self.logoTopLayoutConstraint.constant = 24.f; - self.confirmTextTopLayoutConstraint.constant = 24.f; - self.confirmTextBottomLayoutConstraint.constant = 55.f; - self.confirmAccountButtonTopLayoutConstraint.constant = 70.f; - } else if ([[UIDevice currentDevice] iPhone5X]) { + if (UIDevice.currentDevice.iPhone4X) { + self.logoTopLayoutConstraint.constant = 12.f; + self.confirmTextTopLayoutConstraint.constant = 6.f; + self.confirmTextBottomLayoutConstraint.constant = 6.f; + } else if (UIDevice.currentDevice.iPhone5X || (UIDevice.currentDevice.iPadDevice && UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation))) { self.logoTopLayoutConstraint.constant = 24.f; } - if (self.confirmType == ConfirmTypeAccount) { - self.confirmTextLabel.text = AMLocalizedString(@"confirmText", @"Text shown on the confirm account view to remind the user what to do"); - [self.confirmAccountButton setTitle:AMLocalizedString(@"confirmAccountButton", @"Button title that triggers the confirm account action") forState:UIControlStateNormal]; - } else if (self.confirmType == ConfirmTypeEmail) { - self.confirmTextLabel.text = AMLocalizedString(@"verifyYourEmailAddress_description", @"Text shown on the confirm email view to remind the user what to do"); - [self.confirmAccountButton setTitle:AMLocalizedString(@"confirmEmail", @"Button text for the user to confirm their change of email address.") forState:UIControlStateNormal]; - } else if (self.confirmType == ConfirmTypeCancelAccount) { - self.confirmTextLabel.text = AMLocalizedString(@"enterYourPasswordToConfirmThatYouWanToClose", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); - [self.confirmAccountButton setTitle:AMLocalizedString(@"closeAccount", @"Account closure, password check dialog when user click on closure email.") forState:UIControlStateNormal]; + switch (self.urlType) { + case URLTypeConfirmationLink: + self.confirmTextLabel.text = AMLocalizedString(@"confirmText", @"Text shown on the confirm account view to remind the user what to do"); + [self.confirmAccountButton setTitle:AMLocalizedString(@"Confirm account", @"Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible).") forState:UIControlStateNormal]; + + break; + + case URLTypeChangeEmailLink: + self.confirmTextLabel.text = AMLocalizedString(@"verifyYourEmailAddress_description", @"Text shown on the confirm email view to remind the user what to do"); + [self.confirmAccountButton setTitle:AMLocalizedString(@"confirmEmail", @"Button text for the user to confirm their change of email address.") forState:UIControlStateNormal]; + + break; + + case URLTypeCancelAccountLink: + self.confirmTextLabel.text = AMLocalizedString(@"enterYourPasswordToConfirmThatYouWanToClose", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); + [self.confirmAccountButton setTitle:AMLocalizedString(@"closeAccount", @"Account closure, password check dialog when user click on closure email.") forState:UIControlStateNormal]; + + break; + + default: + break; } [self.cancelButton setTitle:AMLocalizedString(@"cancel", nil) forState:UIControlStateNormal]; - [self.emailTextField setPlaceholder:AMLocalizedString(@"emailPlaceholder", @"Email")]; - self.passwordView.passwordTextField.delegate = self; - self.passwordView.passwordTextField.textColor = UIColor.mnz_black333333; - self.passwordView.passwordTextField.font = [UIFont mnz_SFUIRegularWithSize:17]; + self.emailInputView.inputTextField.text = self.emailString; + self.emailInputView.inputTextField.enabled = NO; + self.emailInputView.inputTextField.keyboardType = UIKeyboardTypeEmailAddress; + if (@available(iOS 11.0, *)) { + self.emailInputView.inputTextField.textContentType = UITextContentTypeUsername; + } - [self.emailTextField setText:_emailString]; + self.passwordView.passwordTextField.delegate = self; + if (@available(iOS 11.0, *)) { + self.passwordView.passwordTextField.textContentType = UITextContentTypePassword; + } } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - if ([[UIDevice currentDevice] iPhoneDevice]) { + if (UIDevice.currentDevice.iPhoneDevice) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; } @@ -76,17 +94,29 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { #pragma mark - IBActions -- (IBAction)confirmTouchUpInside:(id)sender { +- (IBAction)confirmTouchUpInside:(UIButton *)sender { if ([MEGAReachabilityManager isReachableHUDIfNot]) { if ([self validateForm]) { [SVProgressHUD show]; [self lockUI:YES]; - if (self.confirmType == ConfirmTypeAccount) { - [[MEGASdkManager sharedMEGASdk] confirmAccountWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; - } else if (self.confirmType == ConfirmTypeEmail) { - [[MEGASdkManager sharedMEGASdk] confirmChangeEmailWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; - } else if (self.confirmType == ConfirmTypeCancelAccount) { - [[MEGASdkManager sharedMEGASdk] confirmCancelAccountWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; + switch (self.urlType) { + case URLTypeConfirmationLink: + [[MEGASdkManager sharedMEGASdk] confirmAccountWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; + + break; + + case URLTypeChangeEmailLink: + [[MEGASdkManager sharedMEGASdk] confirmChangeEmailWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; + + break; + + case URLTypeCancelAccountLink: + [[MEGASdkManager sharedMEGASdk] confirmCancelAccountWithLink:self.confirmationLinkString password:self.passwordView.passwordTextField.text delegate:self]; + + break; + + default: + break; } } } @@ -95,11 +125,15 @@ - (IBAction)confirmTouchUpInside:(id)sender { - (IBAction)cancelTouchUpInside:(UIButton *)sender { [self.passwordView.passwordTextField resignFirstResponder]; - if (self.confirmType == ConfirmTypeAccount) { + if (self.urlType == URLTypeConfirmationLink) { NSString *message = AMLocalizedString(@"areYouSureYouWantToAbortTheRegistration", @"Asking whether the user really wants to abort/stop the registration process or continue on."); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [MEGALinkManager resetLinkAndURLType]; + }]]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [MEGALinkManager resetLinkAndURLType]; + if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionId"]) { [[MEGASdkManager sharedMEGASdk] logout]; [Helper clearEphemeralSession]; @@ -116,27 +150,23 @@ - (IBAction)cancelTouchUpInside:(UIButton *)sender { #pragma mark - Private - (BOOL)validateForm { - if (self.passwordView.passwordTextField.text.length == 0) { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; + BOOL validPassword = !self.passwordView.passwordTextField.text.mnz_isEmpty; + + if (validPassword) { + [self.passwordView setErrorState:NO]; + } else { + [self.passwordView setErrorState:YES withText:AMLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; [self.passwordView.passwordTextField becomeFirstResponder]; - return NO; } - return YES; + + return validPassword; } - (void)lockUI:(BOOL)boolValue { self.passwordView.passwordTextField.enabled = !boolValue; - self.confirmAccountButton.enabled = !boolValue; self.cancelButton.enabled = !boolValue; } -- (void)showErrorInPasswordView:(BOOL)showError { - self.passwordViewHeightConstraint.constant = showError ? 83.f : 44.f; - self.passwordView.wrongPasswordView.hidden = !showError; - - self.confirmAccountButtonTopLayoutConstraint.constant += (showError ? -39.f : 39.f); -} - #pragma mark - UIResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { @@ -145,24 +175,28 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { #pragma mark - UITextFieldDelegate -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - [self.passwordView.passwordTextField resignFirstResponder]; - return YES; -} - - (void)textFieldDidBeginEditing:(UITextField *)textField { - if (!self.passwordView.wrongPasswordView.hidden) { - [self showErrorInPasswordView:NO]; - } - - self.passwordView.rightImageView.hidden = NO; + self.passwordView.toggleSecureButton.hidden = NO; } - (void)textFieldDidEndEditing:(UITextField *)textField { + [self validateForm]; self.passwordView.passwordTextField.secureTextEntry = YES; [self.passwordView configureSecureTextEntry]; } +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + [self.passwordView setErrorState:NO]; + + return YES; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [self.passwordView.passwordTextField resignFirstResponder]; + [self confirmTouchUpInside:self.confirmAccountButton]; + return YES; +} + #pragma mark - MEGARequestDelegate - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { @@ -173,13 +207,14 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [self lockUI:NO]; switch ([error type]) { + case MEGAErrorTypeApiEKey: case MEGAErrorTypeApiENoent: { //MEGARequestTypeConfirmAccount, MEGARequestTypeConfirmChangeEmailLink, MEGARequestTypeConfirmCancelLink - [self showErrorInPasswordView:YES]; + [self.passwordView setErrorState:YES]; + [self.passwordView.passwordTextField becomeFirstResponder]; break; } case MEGAErrorTypeApiEAccess: { - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"alreadyLoggedInAlertTitle", @"Warning title shown when you try to confirm an account but you are logged in with another one") message:AMLocalizedString(@"alreadyLoggedInAlertMessage", @"Warning message shown when you try to confirm an account but you are logged in with another one") preferredStyle:UIAlertControllerStyleAlert]; [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; @@ -197,10 +232,13 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } case MEGAErrorTypeApiEExist: { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; + [self.emailInputView setErrorState:YES withText:AMLocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; break; } - + + case MEGAErrorTypeApiESid: + break; + default: [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ (%ld)", error.name, (long)error.type]]; break; @@ -210,7 +248,6 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } switch ([request type]) { - case MEGARequestTypeConfirmAccount: { if ([MEGASdkManager sharedMEGAChatSdk] == nil) { [MEGASdkManager createSharedMEGAChatSdk]; @@ -222,9 +259,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [[MEGASdkManager sharedMEGAChatSdk] logout]; } - if (![api isLoggedIn] || [api isLoggedIn] <= 1) { + if ([api isLoggedIn] <= 1) { MEGALoginRequestDelegate *loginRequestDelegate = [[MEGALoginRequestDelegate alloc] init]; - [api loginWithEmail:[self.emailTextField text] password:[self.passwordView.passwordTextField text] delegate:loginRequestDelegate]; + [api loginWithEmail:self.emailInputView.inputTextField.text password:self.passwordView.passwordTextField.text delegate:loginRequestDelegate]; [Helper clearEphemeralSession]; } @@ -247,7 +284,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [newEmailAddressAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:newEmailAddressAlertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:newEmailAddressAlertController animated:YES completion:nil]; }]; break; } diff --git a/iMEGA/Login/CreateAccountViewController.m b/iMEGA/Login/CreateAccountViewController.m index 97b760518c..65adc7bf9c 100644 --- a/iMEGA/Login/CreateAccountViewController.m +++ b/iMEGA/Login/CreateAccountViewController.m @@ -4,29 +4,37 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" -#import "Helper.h" +#import "InputView.h" #import "MEGACreateAccountRequestDelegate.h" #import "MEGAReachabilityManager.h" +#import "NSURL+MNZCategory.h" #import "NSString+MNZCategory.h" #import "CheckEmailAndFollowTheLinkViewController.h" #import "PasswordStrengthIndicatorView.h" #import "PasswordView.h" -@interface CreateAccountViewController () +typedef NS_ENUM(NSInteger, TextFieldTag) { + FirstNameTextFieldTag = 0, + LastNameTextFieldTag, + EmailTextFieldTag, + PasswordTextFieldTag, + RetypeTextFieldTag +}; + +@interface CreateAccountViewController () @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelBarButtonItem; -@property (weak, nonatomic) IBOutlet UITextField *nameTextField; -@property (weak, nonatomic) IBOutlet UITextField *lastNameTextField; -@property (weak, nonatomic) IBOutlet UIImageView *nameIconImageView; -@property (weak, nonatomic) IBOutlet UITextField *emailTextField; -@property (weak, nonatomic) IBOutlet UIImageView *emailIconImageView; -@property (weak, nonatomic) IBOutlet PasswordView *passwordView; +@property (weak, nonatomic) IBOutlet UIScrollView *scrollView; +@property (weak, nonatomic) IBOutlet InputView *firstNameInputView; +@property (weak, nonatomic) IBOutlet InputView *lastNameInputView; +@property (weak, nonatomic) IBOutlet InputView *emailInputView; + +@property (weak, nonatomic) IBOutlet PasswordView *passwordView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *passwordStrengthIndicatorViewHeightLayoutConstraint; @property (weak, nonatomic) IBOutlet PasswordStrengthIndicatorView *passwordStrengthIndicatorView; - @property (weak, nonatomic) IBOutlet PasswordView *retypePasswordView; @property (weak, nonatomic) IBOutlet UIButton *termsCheckboxButton; @@ -34,11 +42,8 @@ @interface CreateAccountViewController () PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; + [self.firstNameInputView setErrorState:NO withText:AMLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; + break; + + case LastNameTextFieldTag: + createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; + [self.lastNameInputView setErrorState:NO withText:AMLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; + break; + + case EmailTextFieldTag: + createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; + [self.emailInputView setErrorState:NO withText:AMLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + break; + + case PasswordTextFieldTag: + createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !text.mnz_isEmpty && [text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; + [self.passwordView setErrorState:NO withText:AMLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; + break; + + case RetypeTextFieldTag: + createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; + [self.retypePasswordView setErrorState:NO withText:AMLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; + break; + + default: + break; + } - if (textField.tag == 0 || textField.tag == 1) { - self.nameIconImageView.tintColor = UIColor.mnz_gray777777; - } else if (textField.tag == 2) { - self.emailIconImageView.image = [UIImage imageNamed:@"email"]; - } else if (textField.tag == 3) { + if (textField.tag == PasswordTextFieldTag) { if (text.length == 0) { self.passwordStrengthIndicatorView.customView.hidden = YES; self.passwordStrengthIndicatorViewHeightLayoutConstraint.constant = 0; } else { self.passwordStrengthIndicatorViewHeightLayoutConstraint.constant = 112.0f; self.passwordStrengthIndicatorView.customView.hidden = NO; - [self.scrollView scrollRectToVisible:self.passwordStrengthIndicatorView.frame animated:YES]; - [self.passwordStrengthIndicatorView updateViewWithPasswordStrength:[[MEGASdkManager sharedMEGASdk] passwordStrength:text]]; } - [self hidePasswordErrorView:self.passwordView constraint:self.passwordViewHeightConstraint]; - [self hidePasswordErrorView:self.retypePasswordView constraint:self.retypePasswordViewHeightConstraint]; - } else if (textField.tag == 4) { - [self hidePasswordErrorView:self.passwordView constraint:self.passwordViewHeightConstraint]; - [self hidePasswordErrorView:self.retypePasswordView constraint:self.retypePasswordViewHeightConstraint]; - } - - return YES; -} - -- (BOOL)textFieldShouldClear:(UITextField *)textField { - - if (textField.tag == 0 || textField.tag == 1) { - self.nameIconImageView.tintColor = UIColor.mnz_gray777777; - } else if (textField.tag == 2) { - self.emailIconImageView.tintColor = UIColor.mnz_gray777777; - } else if (textField.tag == 3) { - self.passwordStrengthIndicatorView.customView.hidden = YES; - self.passwordStrengthIndicatorViewHeightLayoutConstraint.constant = 0; - [self hidePasswordErrorView:self.passwordView constraint:self.passwordViewHeightConstraint]; - [self hidePasswordErrorView:self.retypePasswordView constraint:self.retypePasswordViewHeightConstraint]; - } else if (textField.tag == 4) { - [self hidePasswordErrorView:self.passwordView constraint:self.passwordViewHeightConstraint]; - [self hidePasswordErrorView:self.retypePasswordView constraint:self.retypePasswordViewHeightConstraint]; } - self.createAccountButton.backgroundColor = UIColor.mnz_grayEEEEEE; - return YES; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { switch (textField.tag) { case 0: - [self.lastNameTextField becomeFirstResponder]; + [self.lastNameInputView.inputTextField becomeFirstResponder]; break; case 1: - [self.emailTextField becomeFirstResponder]; + [self.emailInputView.inputTextField becomeFirstResponder]; break; case 2: @@ -458,8 +462,10 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField { return YES; } +#pragma mark - UIGestureRecognizerDelegate + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { - if ((touch.view == self.passwordView.rightImageView || touch.view == self.retypePasswordView.rightImageView) && (gestureRecognizer == self.tapGesture)) { + if ((touch.view == self.passwordView.toggleSecureButton || touch.view == self.retypePasswordView.toggleSecureButton) && (gestureRecognizer == self.tapGesture)) { return NO; } return YES; diff --git a/iMEGA/Login/LoginViewController.h b/iMEGA/Login/LoginViewController.h index 77e18a1db2..e63203e6eb 100644 --- a/iMEGA/Login/LoginViewController.h +++ b/iMEGA/Login/LoginViewController.h @@ -5,4 +5,6 @@ @property (strong, nonatomic) NSString *emailString; +- (void)cleanPasswordTextField; + @end diff --git a/iMEGA/Login/LoginViewController.m b/iMEGA/Login/LoginViewController.m index 42cf09e263..65ee745a77 100644 --- a/iMEGA/Login/LoginViewController.m +++ b/iMEGA/Login/LoginViewController.m @@ -4,18 +4,25 @@ #import "SVProgressHUD.h" #import "Helper.h" +#import "InputView.h" #import "MEGAMultiFactorAuthCheckRequestDelegate.h" #import "MEGANavigationController.h" #import "MEGALoginRequestDelegate.h" #import "MEGALogger.h" #import "MEGAReachabilityManager.h" #import "MEGASdkManager.h" +#import "NSURL+MNZCategory.h" #import "NSString+MNZCategory.h" #import "CreateAccountViewController.h" #import "TwoFactorAuthenticationViewController.h" #import "PasswordView.h" +typedef NS_ENUM(NSInteger, TextFieldTag) { + EmailTextFieldTag = 0, + PasswordTextFieldTag +}; + @interface LoginViewController () @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelBarButtonItem; @@ -23,7 +30,7 @@ @interface LoginViewController () @property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *logoTopLayoutConstraint; -@property (weak, nonatomic) IBOutlet UITextField *emailTextField; +@property (weak, nonatomic) IBOutlet InputView *emailInputView; @property (weak, nonatomic) IBOutlet PasswordView *passwordView; @property (weak, nonatomic) IBOutlet UIButton *loginButton; @@ -40,8 +47,8 @@ @implementation LoginViewController - (void)viewDidLoad { [super viewDidLoad]; - if (([[UIDevice currentDevice] iPhone4X])) { - self.logoTopLayoutConstraint.constant = 24.f; + if (UIDevice.currentDevice.iPhone4X) { + self.logoTopLayoutConstraint.constant = 12.f; } UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(logoTappedFiveTimes:)]; @@ -52,15 +59,22 @@ - (void)viewDidLoad { self.cancelBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); - [self.emailTextField setPlaceholder:AMLocalizedString(@"emailPlaceholder", @"Email")]; + self.emailInputView.inputTextField.returnKeyType = UIReturnKeyNext; + self.emailInputView.inputTextField.delegate = self; + self.emailInputView.inputTextField.tag = EmailTextFieldTag; + self.emailInputView.inputTextField.keyboardType = UIKeyboardTypeEmailAddress; + if (@available(iOS 11.0, *)) { + self.emailInputView.inputTextField.textContentType = UITextContentTypeUsername; + } + self.passwordView.passwordTextField.delegate = self; - self.passwordView.passwordTextField.tag = 1; - self.passwordView.passwordTextField.textColor = UIColor.mnz_black333333; - self.passwordView.passwordTextField.font = [UIFont mnz_SFUIRegularWithSize:17]; - - [self.loginButton setTitle:AMLocalizedString(@"login", @"Login") forState:UIControlStateNormal]; - self.loginButton.backgroundColor = UIColor.mnz_grayEEEEEE; + self.passwordView.passwordTextField.tag = PasswordTextFieldTag; + if (@available(iOS 11.0, *)) { + self.passwordView.passwordTextField.textContentType = UITextContentTypePassword; + } + [self.loginButton setTitle:AMLocalizedString(@"login", @"Login") forState:UIControlStateNormal]; + [self.createAccountButton setTitle:AMLocalizedString(@"createAccount", nil) forState:UIControlStateNormal]; NSString *forgotPasswordString = AMLocalizedString(@"forgotPassword", @"An option to reset the password."); forgotPasswordString = [forgotPasswordString stringByReplacingOccurrencesOfString:@"?" withString:@""]; @@ -74,19 +88,15 @@ - (void)viewWillAppear:(BOOL)animated { [self.navigationItem setTitle:AMLocalizedString(@"login", nil)]; if (self.emailString) { - self.emailTextField.text = self.emailString; + self.emailInputView.inputTextField.text = self.emailString; self.emailString = nil; [self.passwordView.passwordTextField becomeFirstResponder]; - } else { - self.emailTextField.placeholder = AMLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email"); } - - [[MEGALogger sharedLogger] enableChatlogs]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - if ([[UIDevice currentDevice] iPhoneDevice]) { + if (UIDevice.currentDevice.iPhoneDevice) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; } @@ -108,7 +118,7 @@ - (IBAction)tapLogin:(id)sender { } } - [self.emailTextField resignFirstResponder]; + [self.emailInputView.inputTextField resignFirstResponder]; [self.passwordView.passwordTextField resignFirstResponder]; if ([self validateForm]) { @@ -118,19 +128,19 @@ - (IBAction)tapLogin:(id)sender { if (error.type == MEGAErrorTypeApiEMFARequired) { TwoFactorAuthenticationViewController *twoFactorAuthenticationVC = [[UIStoryboard storyboardWithName:@"TwoFactorAuthentication" bundle:nil] instantiateViewControllerWithIdentifier:@"TwoFactorAuthenticationViewControllerID"]; twoFactorAuthenticationVC.twoFAMode = TwoFactorAuthenticationLogin; - twoFactorAuthenticationVC.email = self.emailTextField.text; + twoFactorAuthenticationVC.email = self.emailInputView.inputTextField.text; twoFactorAuthenticationVC.password = self.passwordView.passwordTextField.text; [self.navigationController pushViewController:twoFactorAuthenticationVC animated:YES]; } }; - [[MEGASdkManager sharedMEGASdk] loginWithEmail:self.emailTextField.text password:self.passwordView.passwordTextField.text delegate:loginRequestDelegate]; + [[MEGASdkManager sharedMEGASdk] loginWithEmail:self.emailInputView.inputTextField.text password:self.passwordView.passwordTextField.text delegate:loginRequestDelegate]; } } } - (IBAction)forgotPasswordTouchUpInside:(UIButton *)sender { - [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:@"https://mega.nz/recovery"]]; + [[NSURL URLWithString:@"https://mega.nz/recovery"] mnz_presentSafariViewController]; } - (IBAction)cancel:(UIBarButtonItem *)sender { @@ -153,45 +163,46 @@ - (void)logoPressedFiveSeconds:(UITapGestureRecognizer *)sender { } - (BOOL)validateForm { - if (!self.emailTextField.text.mnz_isValidEmail) { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"emailInvalidFormat", @"Enter a valid email")]; - [self.emailTextField becomeFirstResponder]; - return NO; - } else if (![self validatePassword:self.passwordView.passwordTextField.text]) { - [SVProgressHUD showErrorWithStatus:AMLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; - [self.passwordView.passwordTextField becomeFirstResponder]; - return NO; + BOOL valid = YES; + if (![self validateEmail]) { + [self.emailInputView.inputTextField becomeFirstResponder]; + + valid = NO; } - return YES; + + if (![self validatePassword]) { + if (valid) { + [self.passwordView.passwordTextField becomeFirstResponder]; + } + + valid = NO; + } + + return valid; } -- (BOOL)validatePassword:(NSString *)password { - if (password.length == 0) { - return NO; +- (BOOL)validateEmail { + BOOL validEmail = self.emailInputView.inputTextField.text.mnz_isValidEmail; + + if (validEmail) { + [self.emailInputView setErrorState:NO withText:AMLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; } else { - return YES; + [self.emailInputView setErrorState:YES withText:AMLocalizedString(@"emailInvalidFormat", @"Enter a valid email")]; } + + return validEmail; } -- (BOOL)isEmptyAnyTextFieldForTag:(NSInteger )tag { - BOOL isAnyTextFieldEmpty = NO; - switch (tag) { - case 0: { - if ([self.passwordView.passwordTextField.text isEqualToString:@""]) { - isAnyTextFieldEmpty = YES; - } - break; - } - - case 1: { - if ([self.emailTextField.text isEqualToString:@""]) { - isAnyTextFieldEmpty = YES; - } - break; - } - } +- (BOOL)validatePassword { + BOOL validPassword = !self.passwordView.passwordTextField.text.mnz_isEmpty; - return isAnyTextFieldEmpty; + if (validPassword) { + [self.passwordView setErrorState:NO]; + } else { + [self.passwordView setErrorState:YES withText:AMLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; + } + + return validPassword; } - (NSString *)timeFormatted:(NSUInteger)totalSeconds { @@ -206,6 +217,10 @@ - (NSString *)timeFormatted:(NSUInteger)totalSeconds { return [dateFormatter stringFromDate:date]; } +- (void)cleanPasswordTextField { + self.passwordView.passwordTextField.text = nil; +} + #pragma mark - UIResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { @@ -224,35 +239,61 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { #pragma mark - UITextFieldDelegate +- (void)textFieldDidBeginEditing:(UITextField *)textField { + if (textField.tag == PasswordTextFieldTag) { + self.passwordView.toggleSecureButton.hidden = NO; + } +} + +- (void)textFieldDidEndEditing:(UITextField *)textField { + switch (textField.tag) { + case EmailTextFieldTag: + [self validateEmail]; + + break; + + case PasswordTextFieldTag: + self.passwordView.passwordTextField.secureTextEntry = YES; + [self.passwordView configureSecureTextEntry]; + [self validatePassword]; + + break; + + default: + break; + } +} + - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { - NSString *text = [textField.text stringByReplacingCharactersInRange:range withString:string]; - BOOL shoulBeLoginButtonGray = NO; - if ([text isEqualToString:@""] || (!self.emailTextField.text.mnz_isValidEmail)) { - shoulBeLoginButtonGray = YES; - } else { - shoulBeLoginButtonGray = [self isEmptyAnyTextFieldForTag:textField.tag]; + switch (textField.tag) { + case EmailTextFieldTag: + [self.emailInputView setErrorState:NO withText:AMLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + break; + + case PasswordTextFieldTag: + [self.passwordView setErrorState:NO]; + break; + + default: + break; } - self.loginButton.backgroundColor = shoulBeLoginButtonGray ? UIColor.mnz_grayEEEEEE : UIColor.mnz_redMain; - return YES; } - (BOOL)textFieldShouldClear:(UITextField *)textField { - self.loginButton.backgroundColor = UIColor.mnz_grayEEEEEE; - return YES; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { switch (textField.tag) { - case 0: + case EmailTextFieldTag: [self.passwordView.passwordTextField becomeFirstResponder]; break; - case 1: + case PasswordTextFieldTag: [self.passwordView.passwordTextField resignFirstResponder]; - [self tapLogin:self.loginButton]; + [self tapLogin:self.loginButton]; break; default: @@ -262,17 +303,4 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField { return YES; } -- (void)textFieldDidBeginEditing:(UITextField *)textField { - if (textField.tag == 1) { - self.passwordView.rightImageView.hidden = NO; - } -} - -- (void)textFieldDidEndEditing:(UITextField *)textField { - if (textField.tag == 1) { - self.passwordView.passwordTextField.secureTextEntry = YES; - [self.passwordView configureSecureTextEntry]; - } -} - @end diff --git a/iMEGA/Login/Main.storyboard b/iMEGA/Login/Main.storyboard index 6f52cc0ce8..a0f9bac34e 100644 --- a/iMEGA/Login/Main.storyboard +++ b/iMEGA/Login/Main.storyboard @@ -1,18 +1,15 @@ - + - + - - SFUIDisplay-Semibold - SFUIText-Regular @@ -87,50 +84,54 @@ - - + + - - + + - - + + - - - - - - - - - - - + + + + - + - + - + - - - - - - - + + + - + + + + + + + + + + + + + + + + - + - - + + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - + + @@ -253,11 +222,11 @@ - + - + @@ -277,113 +246,122 @@ - + - - + + - - + + - - - - - - - - - - + + + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + - - - - - - - - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + - + + + + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + + + + - - - - - + - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + - + - - - - - + + + + + - - - - - - - - - - - @@ -514,7 +461,7 @@ - + @@ -536,17 +483,13 @@ - - - - - + + + - + - - @@ -565,78 +508,69 @@ - - - - - + - - - + - - + - - - + + + + + + - - + + - - - + + + + - + - + - + - - - - - - - + + + - + + + + + + + + + + + + + + + + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - + - - - + + @@ -700,15 +625,13 @@ - - + - - + + - + - @@ -738,51 +661,36 @@ - - - - - - - - - + + + - - + + - - - + + + + - + - + - + - - - - - - - - - - - + - - - - - - + - - - - + + + - - - - - - + - - - + - + - + - + @@ -875,7 +770,7 @@ - + @@ -920,10 +815,13 @@ - + + + + - + diff --git a/iMEGA/Login/MainTabBarController.h b/iMEGA/Login/MainTabBarController.h index 6ad8f7e584..d7b56031df 100644 --- a/iMEGA/Login/MainTabBarController.h +++ b/iMEGA/Login/MainTabBarController.h @@ -11,8 +11,12 @@ static NSInteger const MYACCOUNT = 4; @interface MainTabBarController : UITabBarController -- (void)setBadgeValueForChats; - @property (nonatomic, strong) MEGACallManager *megaCallManager; +- (void)openChatRoomNumber:(NSNumber *)chatNumber; +- (void)showAchievements; +- (void)showOffline; + +- (void)setBadgeValueForChats; + @end diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index 4272ec2c6c..3ac7becb6a 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -4,12 +4,17 @@ #import #import "CallViewController.h" +#import "ChatRoomsViewController.h" +#import "DevicePermissionsHelper.h" +#import "Helper.h" +#import "MEGANavigationController.h" #import "MEGAProviderDelegate.h" -#import "MessagesViewController.h" #import "MEGAChatCall+MNZCategory.h" +#import "MyAccountHallViewController.h" +#import "MEGAUserAlertList+MNZCategory.h" +#import "MessagesViewController.h" #import "NSString+MNZCategory.h" #import "UIApplication+MNZCategory.h" -#import "DevicePermissionsHelper.h" @interface MainTabBarController () @@ -36,6 +41,10 @@ - (void)viewDidLoad { for (NSInteger i = 0; i < [defaultViewControllersMutableArray count]; i++) { UITabBarItem *tabBarItem = [[defaultViewControllersMutableArray objectAtIndex:i] tabBarItem]; + if (@available(iOS 10.0, *)) { + tabBarItem.badgeColor = UIColor.clearColor; + [tabBarItem setBadgeTextAttributes:@{ NSForegroundColorAttributeName: UIColor.mnz_redMain } forState:UIControlStateNormal]; + } [self reloadInsetsForTabBarItem:tabBarItem]; switch (tabBarItem.tag) { case CLOUD: @@ -81,7 +90,7 @@ - (void)viewDidLoad { [[MEGASdkManager sharedMEGAChatSdk] addChatCallDelegate:self]; [self setBadgeValueForChats]; - [self setBadgeValueForIncomingContactRequests]; + [self setBadgeValueForMyAccount]; if (@available(iOS 10.0, *)) { _megaCallManager = [[MEGACallManager alloc] init]; @@ -139,6 +148,43 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { } } +#pragma mark - Public + +- (void)openChatRoomNumber:(NSNumber *)chatNumber { + if (chatNumber) { + self.selectedIndex = CHAT; + MEGANavigationController *navigationController = [self.childViewControllers objectAtIndex:CHAT]; + ChatRoomsViewController *chatRoomsVC = navigationController.viewControllers.firstObject; + + if ([MEGASdkManager sharedMEGAChatSdk].numCalls == 0) { + UIViewController *rootViewController = UIApplication.sharedApplication.delegate.window.rootViewController; + if (rootViewController.presentedViewController) { + [rootViewController dismissViewControllerAnimated:YES completion:^{ + [chatRoomsVC openChatRoomWithID:chatNumber.unsignedLongLongValue]; + }]; + } else { + [chatRoomsVC openChatRoomWithID:chatNumber.unsignedLongLongValue]; + } + } + } +} + +- (void)showAchievements { + self.selectedIndex = MYACCOUNT; + MEGANavigationController *navigationController = [self.childViewControllers objectAtIndex:MYACCOUNT]; + MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; + if ([[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { + [myAccountHallVC openAchievements]; + } +} + +- (void)showOffline { + self.selectedIndex = MYACCOUNT; + MEGANavigationController *navigationController = [self.childViewControllers objectAtIndex:MYACCOUNT]; + MyAccountHallViewController *myAccountHallVC = navigationController.viewControllers.firstObject; + [myAccountHallVC openOffline]; +} + #pragma mark - Private - (void)reloadInsetsForTabBarItem:(UITabBarItem *)tabBarItem { @@ -153,20 +199,30 @@ - (void)reloadInsetsForTabBarItem:(UITabBarItem *)tabBarItem { } } -- (void)setBadgeValueForIncomingContactRequests { - MEGAContactRequestList *incomingContactsLists = [[MEGASdkManager sharedMEGASdk] incomingContactRequests]; - long incomingContacts = incomingContactsLists.size.longLongValue; - NSString *badgeValue = incomingContacts ? [NSString stringWithFormat:@"%ld", incomingContacts] : nil; +- (void)setBadgeValueForMyAccount { + int incomingContacts = [[MEGASdkManager sharedMEGASdk] incomingContactRequests].size.intValue; + NSUInteger unseenUserAlerts = [MEGASdkManager sharedMEGASdk].userAlertList.mnz_unseenCount; + + NSString *badgeValue; + NSUInteger total = incomingContacts + unseenUserAlerts; + if (@available(iOS 10.0, *)) { + badgeValue = total ? @"⦁" : nil; + } else { + badgeValue = total ? [NSString stringWithFormat:@"%tu", total] : nil; + } [self setBadgeValue:badgeValue tabPosition:MYACCOUNT]; } - (void)setBadgeValueForChats { NSInteger unreadChats = ([MEGASdkManager sharedMEGAChatSdk] != nil) ? [[MEGASdkManager sharedMEGAChatSdk] unreadChats] : 0; - NSString *badgeValue = unreadChats ? [NSString stringWithFormat:@"%ld", unreadChats] : nil; + NSString *badgeValue; + if (@available(iOS 10.0, *)) { + badgeValue = unreadChats ? @"⦁" : nil; + } else { + badgeValue = unreadChats ? [NSString stringWithFormat:@"%td", unreadChats] : nil; + } [self setBadgeValue:badgeValue tabPosition:CHAT]; - - [UIApplication sharedApplication].applicationIconBadgeNumber = unreadChats; } - (void)setBadgeValue:(NSString *)badgeValue tabPosition:(NSInteger)tabPosition { @@ -193,7 +249,7 @@ - (void)presentRingingCall:(MEGAChatSdk *)api call:(MEGAChatCall *)call { callVC.chatRoom = chatRoom; callVC.videoCall = call.hasRemoteVideo; callVC.callType = CallTypeIncoming; - [UIApplication.mnz_visibleViewController presentViewController:callVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } else { MEGAChatRoom *chatRoom = [api chatRoomForChatId:call.chatId]; UILocalNotification* localNotification = [[UILocalNotification alloc] init]; @@ -224,14 +280,18 @@ - (void)presentCallViewControllerIfThereIsAnIncomingCall { callVC.chatRoom = chatRoom; callVC.videoCall = call.hasRemoteVideo; callVC.callType = CallTypeIncoming; - [UIApplication.mnz_visibleViewController presentViewController:callVC animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } } #pragma mark - MEGAGlobalDelegate - (void)onContactRequestsUpdate:(MEGASdk *)api contactRequestList:(MEGAContactRequestList *)contactRequestList { - [self setBadgeValueForIncomingContactRequests]; + [self setBadgeValueForMyAccount]; +} + +- (void)onUserAlertsUpdate:(MEGASdk *)api userAlertList:(MEGAUserAlertList *)userAlertList { + [self setBadgeValueForMyAccount]; } #pragma mark - MEGAChatDelegate diff --git a/iMEGA/Login/OnboardingInfoView.swift b/iMEGA/Login/OnboardingInfoView.swift index eec28041f6..401400860e 100644 --- a/iMEGA/Login/OnboardingInfoView.swift +++ b/iMEGA/Login/OnboardingInfoView.swift @@ -66,7 +66,7 @@ class OnboardingInfoView: UIView { case .photosPermission: imageView.image = UIImage(named: "photosPermission") titleLabel.text = "Allow Access to Photos".localized(withComment: "Title label that explains that the user is going to be asked for the photos permission") - descriptionLabel.text = "To share photos and videos, allow MEGA to access your photos".localized(withComment: "Detailed explanation of why the user should give permission to access to the photos") + descriptionLabel.text = "Please give the MEGA App permission to access Photos to share photos and videos.".localized(withComment: "Detailed explanation of why the user should give permission to access to the photos") case .microphoneAndCameraPermissions: imageView.image = UIImage(named: "groupChat") titleLabel.text = "Enable Microphone and Camera".localized(withComment: "Title label that explains that the user is going to be asked for the microphone and camera permission") diff --git a/iMEGA/Login/OnboardingViewController.swift b/iMEGA/Login/OnboardingViewController.swift index 63a14be094..b082773aed 100644 --- a/iMEGA/Login/OnboardingViewController.swift +++ b/iMEGA/Login/OnboardingViewController.swift @@ -104,7 +104,7 @@ class OnboardingViewController: UIViewController, UIScrollViewDelegate { pageControl.currentPageIndicatorTintColor = UIColor.mnz_green00BFA5() pageControl.isUserInteractionEnabled = false; - primaryButton.setTitle("Enable Access".localized(withComment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), for: .normal) + primaryButton.setTitle("Allow Access".localized(withComment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), for: .normal) primaryButton.backgroundColor = UIColor.mnz_green00BFA5() secondaryButton.setTitle("notNow".localized(), for: .normal) diff --git a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m index f0986ff1e0..023e318875 100644 --- a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m +++ b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m @@ -106,10 +106,10 @@ - (void)setBonusExpireInLabelText { self.bonusExpireInView.layer.borderColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.14].CGColor; if (awardExpirationdDate.daysUntil > 30) { - bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%lu", awardExpirationdDate.monthsUntil]]; + bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%zd", awardExpirationdDate.monthsUntil]]; bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%2" withString:AMLocalizedString(@"months", @"Used to display the number of months a plan was purchased for e.g. 3 months, 6 months.")]; } else { - bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%lu", awardExpirationdDate.daysUntil]]; + bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%zd", awardExpirationdDate.daysUntil]]; bonusExpiresIn = [bonusExpiresIn stringByReplacingOccurrencesOfString:@"%2" withString:AMLocalizedString(@"days", @"")]; } } diff --git a/iMEGA/My Account/Achievements/AchievementsViewController.m b/iMEGA/My Account/Achievements/AchievementsViewController.m index 4b01ff6bb5..580afefc95 100644 --- a/iMEGA/My Account/Achievements/AchievementsViewController.m +++ b/iMEGA/My Account/Achievements/AchievementsViewController.m @@ -77,8 +77,8 @@ - (NSMutableAttributedString *)textForUnlockedBonuses:(long long)quota { numberFormatter.maximumFractionDigits = 0; NSString *stringFromByteCount; - NSRange firstPartRange = NSMakeRange(0, 0); - NSRange secondPartRange = NSMakeRange(0, 0); + NSRange firstPartRange; + NSRange secondPartRange; stringFromByteCount = [self.byteCountFormatter stringFromByteCount:quota]; @@ -209,7 +209,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } NSDate *awardExpirationdDate = [self.achievementsDetails awardExpirationAtIndex:index.unsignedIntegerValue]; - cell.subtitleLabel.text = (awardExpirationdDate.daysUntil == 0) ? AMLocalizedString(@"expired", @"Label to show that an error related with expiration occurs during a SDK operation.") : [AMLocalizedString(@"xDaysLeft", @"") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%lu", awardExpirationdDate.daysUntil]]; + cell.subtitleLabel.text = (awardExpirationdDate.daysUntil == 0) ? AMLocalizedString(@"expired", @"Label to show that an error related with expiration occurs during a SDK operation.") : [AMLocalizedString(@"xDaysLeft", @"") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%zd", awardExpirationdDate.daysUntil]]; cell.subtitleLabel.textColor = (awardExpirationdDate.daysUntil <= 15) ? UIColor.mnz_redMain : [UIColor mnz_gray666666]; } diff --git a/iMEGA/My Account/Achievements/InviteFriendsViewController.m b/iMEGA/My Account/Achievements/InviteFriendsViewController.m index b35abe65b2..bdcfd9cd32 100644 --- a/iMEGA/My Account/Achievements/InviteFriendsViewController.m +++ b/iMEGA/My Account/Achievements/InviteFriendsViewController.m @@ -153,16 +153,26 @@ - (IBAction)howItWorksTouchUpInside:(UIButton *)sender { - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperties:(NSArray *)contactProperties { if (contactProperties.count != 0) { + BOOL error = NO; for (CNContactProperty *contactProperty in contactProperties) { - [self.tokens addObject:contactProperty.value]; + NSString *email = contactProperty.value; + if (email.mnz_isValidEmail) { + [self.tokens addObject:email]; + } else { + error = YES; + self.inviteButtonUpperLabel.text = [NSString stringWithFormat:@"%@ %@", AMLocalizedString(@"theEmailAddressFormatIsInvalid", @"Add contacts and share dialog error message when user try to add wrong email address"), email]; + self.inviteButtonUpperLabel.textColor = UIColor.mnz_redMain; + } } [self.tokenField reloadData]; self.tokenFieldHeightLayoutConstraint.constant = self.tokenField.frame.size.height; - self.inviteButton.backgroundColor = UIColor.mnz_redMain; + self.inviteButton.backgroundColor = self.tokens.count == 0 ? UIColor.mnz_grayCCCCCC : UIColor.mnz_redMain; - [self cleanErrors]; + if (!error) { + [self cleanErrors]; + } } } diff --git a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m index 21a8047f6f..9a4d1e1644 100644 --- a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m +++ b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m @@ -102,7 +102,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [self setStorageAndTransferQuotaRewardsForCell:cell withAwardId:awardId]; NSDate *awardExpirationdDate = [self.achievementsDetails awardExpirationAtIndex:inviteIndexPath]; - cell.daysLeftTrailingLabel.text = [AMLocalizedString(@"xDaysLeft", @"") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%lu", awardExpirationdDate.daysUntil]]; + cell.daysLeftTrailingLabel.text = [AMLocalizedString(@"xDaysLeft", @"") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%td", awardExpirationdDate.daysUntil]]; cell.daysLeftTrailingLabel.textColor = (awardExpirationdDate.daysUntil <= 15) ? UIColor.mnz_redMain : [UIColor mnz_gray666666]; return cell; diff --git a/iMEGA/My Account/MyAccount.storyboard b/iMEGA/My Account/MyAccount.storyboard index 9e0297f608..960484b5a2 100644 --- a/iMEGA/My Account/MyAccount.storyboard +++ b/iMEGA/My Account/MyAccount.storyboard @@ -1574,7 +1574,7 @@ - + @@ -1593,7 +1593,7 @@ - + diff --git a/iMEGA/My Account/MyAccountBaseViewController.m b/iMEGA/My Account/MyAccountBaseViewController.m index 4cfdbbb3dd..b51d2acd48 100644 --- a/iMEGA/My Account/MyAccountBaseViewController.m +++ b/iMEGA/My Account/MyAccountBaseViewController.m @@ -14,6 +14,7 @@ #import "MEGANavigationController.h" #import "MEGASdkManager.h" #import "MEGAUser+MNZCategory.h" +#import "NSFileManager+MNZCategory.h" #import "UIAlertAction+MNZCategory.h" #import "UIImageView+MNZCategory.h" @@ -191,9 +192,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG NSString *myUserBase64Handle = [MEGASdk base64HandleForUserHandle:[[[MEGASdkManager sharedMEGASdk] myUser] handle]]; NSString *myAvatarFilePath = [[Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"] stringByAppendingPathComponent:myUserBase64Handle]; if (request.file == nil) { - NSError *removeError = nil; - [[NSFileManager defaultManager] removeItemAtPath:myAvatarFilePath error:&removeError]; - if (removeError) MEGALogError(@"Remove item at path failed with error: %@", removeError); + [NSFileManager.defaultManager mnz_removeItemAtPath:myAvatarFilePath]; } [self setUserAvatar]; diff --git a/iMEGA/My Account/MyAccountHallViewController.m b/iMEGA/My Account/MyAccountHallViewController.m index 6c22b49081..cbabd354bd 100644 --- a/iMEGA/My Account/MyAccountHallViewController.m +++ b/iMEGA/My Account/MyAccountHallViewController.m @@ -9,10 +9,12 @@ #import "MEGAPurchase.h" #import "MEGASdk+MNZCategory.h" #import "MEGAUser+MNZCategory.h" +#import "MEGAUserAlertList+MNZCategory.h" #import "MEGAReachabilityManager.h" #import "MEGASdkManager.h" #import "MyAccountHallTableViewCell.h" #import "MyAccountViewController.h" +#import "NotificationsTableViewController.h" #import "SettingsTableViewController.h" #import "TransfersViewController.h" #import "UIImage+MNZCategory.h" @@ -132,7 +134,7 @@ - (void)openAchievements { [self.navigationController popToRootViewControllerAnimated:NO]; } - NSIndexPath *achievementsIndexPath = [NSIndexPath indexPathForRow:2 inSection:0]; + NSIndexPath *achievementsIndexPath = [NSIndexPath indexPathForRow:3 inSection:0]; [self tableView:self.tableView didSelectRowAtIndexPath:achievementsIndexPath]; } @@ -142,7 +144,7 @@ - (void)openOffline { [self.navigationController popToRootViewControllerAnimated:NO]; } - NSIndexPath *offlineIndexPath = [NSIndexPath indexPathForRow:4 inSection:0]; + NSIndexPath *offlineIndexPath = [NSIndexPath indexPathForRow:5 inSection:0]; [self tableView:self.tableView didSelectRowAtIndexPath:offlineIndexPath]; } @@ -167,14 +169,14 @@ - (IBAction)viewAndEditProfileTouchUpInside:(UIButton *)sender { #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return 6; + return 7; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *identifier; if (indexPath.row == 0) { identifier = @"MyAccountHallUsedStorageTableViewCellID"; - } else if (indexPath.row == 2) { + } else if (indexPath.row == 3) { identifier = @"MyAccountHallWithSubtitleTableViewCellID"; } else { identifier = @"MyAccountHallTableViewCellID"; @@ -205,7 +207,25 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - case 1: { //Contacts + case 1: { // Notifications + cell.sectionLabel.text = AMLocalizedString(@"notifications", nil); + cell.iconImageView.image = [UIImage imageNamed:@"Notifications"]; + NSUInteger unseenUserAlerts = [MEGASdkManager sharedMEGASdk].userAlertList.mnz_unseenCount; + if (unseenUserAlerts == 0) { + cell.pendingView.hidden = YES; + cell.pendingLabel.text = nil; + } else { + if (cell.pendingView.hidden) { + cell.pendingView.hidden = NO; + cell.pendingView.clipsToBounds = YES; + } + + cell.pendingLabel.text = [NSString stringWithFormat:@"%tu", unseenUserAlerts]; + } + break; + } + + case 2: { // Contacts cell.sectionLabel.text = AMLocalizedString(@"contactsTitle", @"Title of the Contacts section"); cell.iconImageView.image = [UIImage imageNamed:@"myAccountContactsIcon"]; MEGAContactRequestList *incomingContactsLists = [[MEGASdkManager sharedMEGASdk] incomingContactRequests]; @@ -219,12 +239,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.pendingView.clipsToBounds = YES; } - cell.pendingLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)incomingContacts]; + cell.pendingLabel.text = [NSString stringWithFormat:@"%tu", incomingContacts]; } break; } - case 2: { //Achievements + case 3: { // Achievements cell.sectionLabel.text = AMLocalizedString(@"achievementsTitle", @"Title of the Achievements section"); cell.subtitleLabel.text = AMLocalizedString(@"inviteFriendsAndGetRewards", @"Subtitle show under the Achievements label to explain what is this section"); cell.iconImageView.image = [UIImage imageNamed:@"myAccountAchievementsIcon"]; @@ -233,7 +253,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - case 3: { //Transfers + case 4: { // Transfers cell.sectionLabel.text = AMLocalizedString(@"transfers", @"Title of the Transfers section"); cell.iconImageView.image = [UIImage imageNamed:@"myAccountTransfersIcon"]; cell.pendingView.hidden = YES; @@ -241,7 +261,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - case 4: { //Offline + case 5: { // Offline cell.sectionLabel.text = AMLocalizedString(@"offline", @"Title of the Offline section"); cell.iconImageView.image = [UIImage imageNamed:@"myAccountOfflineIcon"]; cell.pendingView.hidden = YES; @@ -249,8 +269,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - case 5: { - //Settings + case 6: { // Settings cell.sectionLabel.text = AMLocalizedString(@"settingsTitle", @"Title of the Settings section"); cell.iconImageView.image = [UIImage imageNamed:@"myAccountSettingsIcon"]; cell.pendingView.hidden = YES; @@ -266,7 +285,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { CGFloat heightForRow; - if (indexPath.row == 2 && ![[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { + if (indexPath.row == 3 && ![[MEGASdkManager sharedMEGASdk] isAchievementsEnabled]) { heightForRow = 0.0f; } else { heightForRow = 60.0f; @@ -294,31 +313,38 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath break; } - case 1: { //Contacts + + case 1: { // Notifications + NotificationsTableViewController *notificationsTVC = [[UIStoryboard storyboardWithName:@"Notifications" bundle:nil] instantiateViewControllerWithIdentifier:@"NotificationsTableViewControllerID"]; + [self.navigationController pushViewController:notificationsTVC animated:YES]; + break; + } + + case 2: { // Contacts ContactsViewController *contactsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsViewControllerID"]; [self.navigationController pushViewController:contactsVC animated:YES]; break; } - case 2: { //Achievements + case 3: { // Achievements AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; [self.navigationController pushViewController:achievementsVC animated:YES]; break; } - case 3: { //Transfers + case 4: { // Transfers TransfersViewController *transferVC = [[UIStoryboard storyboardWithName:@"Transfers" bundle:nil] instantiateViewControllerWithIdentifier:@"TransfersViewControllerID"]; [self.navigationController pushViewController:transferVC animated:YES]; break; } - case 4: { //Offline + case 5: { // Offline OfflineViewController *offlineVC = [[UIStoryboard storyboardWithName:@"Offline" bundle:nil] instantiateViewControllerWithIdentifier:@"OfflineViewControllerID"]; [self.navigationController pushViewController:offlineVC animated:YES]; break; } - case 5: { //Settings + case 6: { // Settings SettingsTableViewController *settingsTVC = [[UIStoryboard storyboardWithName:@"Settings" bundle:nil] instantiateViewControllerWithIdentifier:@"SettingsTableViewControllerID"]; [self.navigationController pushViewController:settingsTVC animated:YES]; break; @@ -337,10 +363,15 @@ - (void)pricingsReady { #pragma mark - MEGAGlobalDelegate - (void)onContactRequestsUpdate:(MEGASdk *)api contactRequestList:(MEGAContactRequestList *)contactRequestList { - NSIndexPath *contactsIndexPath = [NSIndexPath indexPathForRow:1 inSection:0]; + NSIndexPath *contactsIndexPath = [NSIndexPath indexPathForRow:2 inSection:0]; [self.tableView reloadRowsAtIndexPaths:@[contactsIndexPath] withRowAnimation:UITableViewRowAnimationNone]; } +- (void)onUserAlertsUpdate:(MEGASdk *)api userAlertList:(MEGAUserAlertList *)userAlertList { + NSIndexPath *notificationsIndexPath = [NSIndexPath indexPathForRow:1 inSection:0]; + [self.tableView reloadRowsAtIndexPaths:@[notificationsIndexPath] withRowAnimation:UITableViewRowAnimationNone]; +} + #pragma mark - MEGARequestDelegate - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { diff --git a/iMEGA/My Account/MyAccountViewController.m b/iMEGA/My Account/MyAccountViewController.m index df381c914e..c8fd7fc246 100644 --- a/iMEGA/My Account/MyAccountViewController.m +++ b/iMEGA/My Account/MyAccountViewController.m @@ -120,6 +120,8 @@ - (void)viewWillAppear:(BOOL)animated { } - (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + if (self.presentedViewController == nil) { [[MEGASdkManager sharedMEGASdk] removeMEGARequestDelegate:self]; } diff --git a/iMEGA/My Account/Notifications/NotificationTableViewCell.h b/iMEGA/My Account/Notifications/NotificationTableViewCell.h new file mode 100644 index 0000000000..9c605d7a14 --- /dev/null +++ b/iMEGA/My Account/Notifications/NotificationTableViewCell.h @@ -0,0 +1,17 @@ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NotificationTableViewCell : UITableViewCell + +@property (weak, nonatomic) IBOutlet UILabel *typeLabel; +@property (weak, nonatomic) IBOutlet UIView *theNewView; +@property (weak, nonatomic) IBOutlet UILabel *theNewLabel; +@property (weak, nonatomic) IBOutlet UILabel *headingLabel; +@property (weak, nonatomic) IBOutlet UILabel *contentLabel; +@property (weak, nonatomic) IBOutlet UILabel *dateLabel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/My Account/Notifications/NotificationTableViewCell.m b/iMEGA/My Account/Notifications/NotificationTableViewCell.m new file mode 100644 index 0000000000..780f9e2b5f --- /dev/null +++ b/iMEGA/My Account/Notifications/NotificationTableViewCell.m @@ -0,0 +1,12 @@ + +#import "NotificationTableViewCell.h" + +@implementation NotificationTableViewCell + +- (void)awakeFromNib { + [super awakeFromNib]; + + self.theNewLabel.text = AMLocalizedString(@"New", @"Label shown inside an unseen notification").uppercaseString; +} + +@end diff --git a/iMEGA/My Account/Notifications/Notifications.storyboard b/iMEGA/My Account/Notifications/Notifications.storyboard new file mode 100644 index 0000000000..d1abcbe75e --- /dev/null +++ b/iMEGA/My Account/Notifications/Notifications.storyboard @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.h b/iMEGA/My Account/Notifications/NotificationsTableViewController.h new file mode 100644 index 0000000000..784d1e1528 --- /dev/null +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.h @@ -0,0 +1,10 @@ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NotificationsTableViewController : UITableViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.m b/iMEGA/My Account/Notifications/NotificationsTableViewController.m new file mode 100644 index 0000000000..812cab9ee9 --- /dev/null +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.m @@ -0,0 +1,491 @@ + +#import "NotificationsTableViewController.h" + +#import "DTConstants.h" +#import "UIScrollView+EmptyDataSet.h" + +#import "ContactDetailsViewController.h" +#import "ContactsViewController.h" +#import "Helper.h" +#import "MainTabBarController.h" +#import "MEGANode+MNZCategory.h" +#import "MEGAReachabilityManager.h" +#import "MEGASDKManager.h" +#import "MEGAStore.h" +#import "MEGAUser+MNZCategory.h" +#import "MEGAUserAlert.h" +#import "MEGAUserAlertList+MNZCategory.h" +#import "NotificationTableViewCell.h" +#import "SharedItemsViewController.h" +#import "UIColor+MNZCategory.h" + +@interface NotificationsTableViewController () + +@property (nonatomic) NSArray *userAlertsArray; +@property (nonatomic) NSDateFormatter *dateFormatter; +@property (nonatomic) UIFont *boldFont; + +@end + +@implementation NotificationsTableViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.emptyDataSetSource = self; + + self.navigationItem.title = AMLocalizedString(@"notifications", nil); + self.userAlertsArray = [MEGASdkManager sharedMEGASdk].userAlertList.mnz_userAlertsArray; + + self.dateFormatter = [[NSDateFormatter alloc] init]; + self.dateFormatter.dateStyle = NSDateFormatterLongStyle; + self.dateFormatter.timeStyle = NSDateFormatterShortStyle; + + self.boldFont = [UIFont boldSystemFontOfSize:14.0f]; + + self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(internetConnectionChanged) name:kReachabilityChangedNotification object:nil]; + + [[MEGASdkManager sharedMEGASdk] addMEGAGlobalDelegate:self]; + [[MEGAReachabilityManager sharedManager] retryPendingConnections]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [[MEGASdkManager sharedMEGASdk] removeMEGAGlobalDelegate:self]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + + [[MEGASdkManager sharedMEGASdk] acknowledgeUserAlerts]; +} + +#pragma mark - Private + +- (void)configureTypeLabel:(UILabel *)typeLabel forType:(MEGAUserAlertType)type { + switch (type) { + case MEGAUserAlertTypeIncomingPendingContactRequest: + case MEGAUserAlertTypeIncomingPendingContactCancelled: + case MEGAUserAlertTypeIncomingPendingContactReminder: + case MEGAUserAlertTypeContactChangeDeletedYou: + case MEGAUserAlertTypeContactChangeContactEstablished: + case MEGAUserAlertTypeContactChangeAccountDeleted: + case MEGAUserAlertTypeContactChangeBlockedYou: + case MEGAUserAlertTypeUpdatePendingContactIncomingIgnored: + case MEGAUserAlertTypeUpdatePendingContactIncomingAccepted: + case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: + case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: + case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: + typeLabel.text = AMLocalizedString(@"contactsTitle", @"Title of the Contacts section").uppercaseString; + typeLabel.textColor = UIColor.mnz_green00897B; + break; + + case MEGAUserAlertTypeNewShare: + case MEGAUserAlertTypeDeletedShare: + case MEGAUserAlertTypeNewShareNodes: + case MEGAUserAlertTypeRemovedSharesNodes: + typeLabel.text = AMLocalizedString(@"shared", @"Title of the tab bar item for the Shared Items section").uppercaseString; + typeLabel.textColor = UIColor.mnz_orangeFFA500; + break; + + case MEGAUserAlertTypePaymentSucceeded: + case MEGAUserAlertTypePaymentFailed: + typeLabel.text = AMLocalizedString(@"Payment info", @"The header of a notification related to payments").uppercaseString; + typeLabel.textColor = UIColor.mnz_redMain; + break; + + case MEGAUserAlertTypePaymentReminder: + typeLabel.text = AMLocalizedString(@"PRO membership plan expiring soon", @"A title for a notification saying the user’s pricing plan will expire soon.").uppercaseString; + typeLabel.textColor = UIColor.mnz_redMain; + break; + + case MEGAUserAlertTypeTakedown: + typeLabel.text = AMLocalizedString(@"Takedown notice", @"The header of a notification indicating that a file or folder has been taken down due to infringement or other reason.").uppercaseString; + typeLabel.textColor = UIColor.mnz_redMain; + break; + + case MEGAUserAlertTypeTakedownReinstated: + typeLabel.text = AMLocalizedString(@"Takedown reinstated", @"The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice.").uppercaseString; + typeLabel.textColor = UIColor.mnz_redMain; + break; + + default: + typeLabel.text = nil; + break; + } +} + +- (void)configureHeadingLabel:(UILabel *)headingLabel forAlert:(MEGAUserAlert *)userAlert { + switch (userAlert.type) { + case MEGAUserAlertTypeIncomingPendingContactRequest: + case MEGAUserAlertTypeIncomingPendingContactCancelled: + case MEGAUserAlertTypeIncomingPendingContactReminder: + case MEGAUserAlertTypeContactChangeDeletedYou: + case MEGAUserAlertTypeContactChangeContactEstablished: + case MEGAUserAlertTypeContactChangeAccountDeleted: + case MEGAUserAlertTypeContactChangeBlockedYou: + case MEGAUserAlertTypeUpdatePendingContactIncomingIgnored: + case MEGAUserAlertTypeUpdatePendingContactIncomingAccepted: + case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: + case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: + case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: + case MEGAUserAlertTypeNewShare: + case MEGAUserAlertTypeDeletedShare: + case MEGAUserAlertTypeNewShareNodes: + case MEGAUserAlertTypeRemovedSharesNodes: { + if (userAlert.email) { + headingLabel.hidden = NO; + MOUser *user = [[MEGAStore shareInstance] fetchUserWithEmail:userAlert.email]; + if (user && (user.firstname || user.lastname)) { + headingLabel.text = [NSString stringWithFormat:@"%@ (%@)", user.fullName, user.email]; + } else { + headingLabel.text = userAlert.email; + } + } else { + headingLabel.hidden = YES; + headingLabel.text = nil; + } + break; + } + + default: { + headingLabel.hidden = YES; + headingLabel.text = nil; + break; + } + } +} + +- (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *)userAlert { + switch (userAlert.type) { + case MEGAUserAlertTypeIncomingPendingContactRequest: + contentLabel.text = AMLocalizedString(@"Sent you a contact request", @"When a contact sent a contact/friend request"); + break; + + case MEGAUserAlertTypeIncomingPendingContactCancelled: + contentLabel.text = AMLocalizedString(@"Cancelled their contact request", @"A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request."); + break; + + case MEGAUserAlertTypeIncomingPendingContactReminder: + contentLabel.text = AMLocalizedString(@"Reminder: You have a contact request", @"A reminder notification to remind the user to respond to the contact request."); + break; + + case MEGAUserAlertTypeContactChangeDeletedYou: + contentLabel.text = AMLocalizedString(@"Deleted you as a contact", @"A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact."); + break; + + case MEGAUserAlertTypeContactChangeContactEstablished: + contentLabel.text = AMLocalizedString(@"Contact relationship established", @"A notification telling the user that they are now fully connected with the other user (the users are in each other’s address books)."); + break; + + case MEGAUserAlertTypeContactChangeAccountDeleted: + contentLabel.text = AMLocalizedString(@"Account has been deleted/deactivated", @"A notification telling the user that one of their contact’s accounts has been deleted or deactivated."); + break; + + case MEGAUserAlertTypeContactChangeBlockedYou: + contentLabel.text = AMLocalizedString(@"Blocked you as a contact", @"A notification telling the user that another user blocked them as a contact (they will no longer be able to contact them). E.g. name@email.com blocked you as a contact."); + break; + + case MEGAUserAlertTypeUpdatePendingContactIncomingIgnored: + contentLabel.text = AMLocalizedString(@"You ignored a contact request", @"Response text after clicking Ignore on an incoming contact request notification."); + break; + + case MEGAUserAlertTypeUpdatePendingContactIncomingAccepted: + contentLabel.text = AMLocalizedString(@"You accepted a contact request", @"Response text after clicking Accept on an incoming contact request notification."); + break; + + case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: + contentLabel.text = AMLocalizedString(@"You denied a contact request", @"Response text after clicking Deny on an incoming contact request notification."); + break; + + case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: + contentLabel.text = AMLocalizedString(@"Accepted your contact request", @"When somebody accepted your contact request"); + break; + + case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: + contentLabel.text = AMLocalizedString(@"Denied your contact request", @"When somebody denied your contact request"); + break; + + case MEGAUserAlertTypeNewShare: + contentLabel.text = AMLocalizedString(@"newSharedFolder", @"Notification text body shown when you have received a new shared folder"); + break; + + case MEGAUserAlertTypeDeletedShare: { + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; + if (node && ![userAlert.path hasPrefix:userAlert.email]) { + NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name attributes:@{ NSFontAttributeName : self.boldFont }]; + NSString *text = AMLocalizedString(@"A user has left the shared folder {0}", @"notification text"); + NSRange range = [text rangeOfString:@"{0}"]; + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + [attributedText replaceCharactersInRange:range withAttributedString:nodeName]; + contentLabel.attributedText = attributedText; + } else { + contentLabel.text = AMLocalizedString(@"Access to folders was removed.", @"This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal)."); + } + break; + } + + case MEGAUserAlertTypeNewShareNodes: { + int64_t fileCount = [userAlert numberAtIndex:1]; + int64_t folderCount = [userAlert numberAtIndex:0]; + NSString *text; + if ((folderCount > 1) && (fileCount > 1)) { + text = [[AMLocalizedString(@"Added [A] files and [B] folders", @"Content of a notification that informs how many files and folders have been added to a shared folder") stringByReplacingOccurrencesOfString:@"[A]" withString:[NSString stringWithFormat:@"%lld", fileCount]] stringByReplacingOccurrencesOfString:@"[B]" withString:[NSString stringWithFormat:@"%lld", folderCount]]; + } + else if ((folderCount > 1) && (fileCount == 1)) { + text = [NSString stringWithFormat:AMLocalizedString(@"Added 1 file and %lld folders", @"Content of a notification that informs how many files and folders have been added to a shared folder"), folderCount]; + } + else if ((folderCount == 1) && (fileCount > 1)) { + text = [NSString stringWithFormat:AMLocalizedString(@"Added %lld files and 1 folder", @"Content of a notification that informs how many files and folders have been added to a shared folder"), fileCount]; + } + else if ((folderCount == 1) && (fileCount == 1)) { + text = AMLocalizedString(@"Added 1 file and 1 folder", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + } + else if (folderCount > 1) { + text = [NSString stringWithFormat:AMLocalizedString(@"Added %lld folders", @"Content of a notification that informs how many files and folders have been added to a shared folder"), folderCount]; + } + else if (fileCount > 1) { + text = [NSString stringWithFormat:AMLocalizedString(@"Added %lld files", @"Content of a notification that informs how many files and folders have been added to a shared folder"), fileCount]; + } + else if (folderCount == 1) { + text = AMLocalizedString(@"Added 1 folder", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + } + else if (fileCount == 1) { + text = AMLocalizedString(@"Added 1 file", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + } else { + text = userAlert.title; + } + contentLabel.text = text; + break; + } + + case MEGAUserAlertTypeRemovedSharesNodes: { + int64_t itemCount = [userAlert numberAtIndex:0]; + contentLabel.text = [AMLocalizedString(@"Removed [X] items from a share", @"Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items.") stringByReplacingOccurrencesOfString:@"[X]" withString:[NSString stringWithFormat:@"%lld", itemCount]]; + break; + } + + case MEGAUserAlertTypePaymentSucceeded: { + NSAttributedString *proPlan = [[NSAttributedString alloc] initWithString:[userAlert stringAtIndex:0] attributes:@{ NSFontAttributeName : self.boldFont }]; + NSString *text = AMLocalizedString(@"Your payment for the %1 plan was received.", @"A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III."); + NSRange range = [text rangeOfString:@"%1"]; + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + [attributedText replaceCharactersInRange:range withAttributedString:proPlan]; + contentLabel.attributedText = attributedText; + break; + } + + case MEGAUserAlertTypePaymentFailed: { + NSAttributedString *proPlan = [[NSAttributedString alloc] initWithString:[userAlert stringAtIndex:0] attributes:@{ NSFontAttributeName : self.boldFont }]; + NSString *text = AMLocalizedString(@"Your payment for the %1 plan was unsuccessful.", @"A notification telling the user that their Pro plan payment was unsuccessful. The %1 indicates the name of the Pro plan they were trying to pay for e.g. Lite, PRO II."); + NSRange range = [text rangeOfString:@"%1"]; + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + [attributedText replaceCharactersInRange:range withAttributedString:proPlan]; + contentLabel.attributedText = attributedText; + break; + } + + case MEGAUserAlertTypePaymentReminder: { + NSUInteger days = ([userAlert timestampAtIndex:1] - [NSDate date].timeIntervalSince1970) / SECONDS_IN_DAY; + NSString *text = days == 1 ? AMLocalizedString(@"Your PRO membership plan will expire in 1 day.", @"The professional pricing plan which the user is currently on will expire in one day.") : [AMLocalizedString(@"Your PRO membership plan will expire in %1 days.", @"The professional pricing plan which the user is currently on will expire in 5 days. The %1 is a placeholder for the number of days and should not be removed.") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%tu", days]]; + contentLabel.text = text; + break; + } + + case MEGAUserAlertTypeTakedown: { + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; + NSString *nodeType = @""; + if (node.type == MEGANodeTypeFile) { + nodeType = AMLocalizedString(@"file", nil); + } else if (node.type == MEGANodeTypeFolder) { + nodeType = AMLocalizedString(@"folder", nil); + } + NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name attributes:@{ NSFontAttributeName : self.boldFont }]; + NSString *text = AMLocalizedString(@"Your publicly shared %1 (%2) has been taken down.", @"The text of a notification indicating that a file or folder has been taken down due to infringement or other reason. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder."); + text = [text stringByReplacingOccurrencesOfString:@"%1" withString:nodeType.lowercaseString]; + NSRange range = [text rangeOfString:@"%2"]; + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + [attributedText replaceCharactersInRange:range withAttributedString:nodeName]; + contentLabel.attributedText = attributedText; + break; + } + + case MEGAUserAlertTypeTakedownReinstated:{ + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; + NSString *nodeType = @""; + if (node.type == MEGANodeTypeFile) { + nodeType = AMLocalizedString(@"file", nil); + } else if (node.type == MEGANodeTypeFolder) { + nodeType = AMLocalizedString(@"folder", nil); + } + NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name attributes:@{ NSFontAttributeName : self.boldFont }]; + NSString *text = AMLocalizedString(@"Your taken down %1 (%2) has been reinstated.", @"The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder."); + text = [text stringByReplacingOccurrencesOfString:@"%1" withString:nodeType.lowercaseString]; + NSRange range = [text rangeOfString:@"%2"]; + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + [attributedText replaceCharactersInRange:range withAttributedString:nodeName]; + contentLabel.attributedText = attributedText; + break; + } + + default: + contentLabel.text = userAlert.title; + break; + } +} + +- (void)internetConnectionChanged { + if ([MEGAReachabilityManager isReachable]) { + self.userAlertsArray = [MEGASdkManager sharedMEGASdk].userAlertList.mnz_userAlertsArray; + } else { + self.userAlertsArray = @[]; + } + [self.tableView reloadData]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.userAlertsArray.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + NotificationTableViewCell *cell = (NotificationTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"notificationCell" forIndexPath:indexPath]; + + MEGAUserAlert *userAlert = [self.userAlertsArray objectAtIndex:indexPath.row]; + + [self configureTypeLabel:cell.typeLabel forType:userAlert.type]; + if (userAlert.isSeen) { + cell.theNewView.hidden = YES; + cell.backgroundColor = UIColor.mnz_grayFAFAFA; + } else { + cell.theNewView.hidden = NO; + cell.backgroundColor = UIColor.whiteColor; + } + [self configureHeadingLabel:cell.headingLabel forAlert:userAlert]; + [self configureContentLabel:cell.contentLabel forAlert:userAlert]; + cell.dateLabel.text = [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:[userAlert timestampAtIndex:0]]]; + + return cell; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self.tableView deselectRowAtIndexPath:indexPath animated:NO]; + + MEGAUserAlert *userAlert = [self.userAlertsArray objectAtIndex:indexPath.row]; + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; + UINavigationController *navigationController = self.navigationController; + + switch (userAlert.type) { + case MEGAUserAlertTypeIncomingPendingContactRequest: + case MEGAUserAlertTypeIncomingPendingContactReminder: { + if ([[MEGASdkManager sharedMEGASdk] incomingContactRequests].size.intValue) { + UINavigationController *contactRequestsNC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsRequestsNavigationControllerID"]; + [self presentViewController:contactRequestsNC animated:YES completion:nil]; + } + break; + } + + case MEGAUserAlertTypeIncomingPendingContactCancelled: + case MEGAUserAlertTypeContactChangeDeletedYou: + case MEGAUserAlertTypeContactChangeAccountDeleted: + case MEGAUserAlertTypeContactChangeBlockedYou: + case MEGAUserAlertTypeUpdatePendingContactIncomingIgnored: + case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: + case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: + break; + + case MEGAUserAlertTypeContactChangeContactEstablished: + case MEGAUserAlertTypeUpdatePendingContactIncomingAccepted: + case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: { + MEGAUser *user = [[MEGASdkManager sharedMEGASdk] contactForEmail:userAlert.email]; + if (user && user.visibility == MEGAUserVisibilityVisible) { + [navigationController popToRootViewControllerAnimated:NO]; + ContactsViewController *contactsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsViewControllerID"]; + contactsVC.avoidPresentIncomingPendingContactRequests = YES; + ContactDetailsViewController *contactDetailsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactDetailsViewControllerID"]; + contactDetailsVC.contactDetailsMode = ContactDetailsModeDefault; + contactDetailsVC.userEmail = user.email; + contactDetailsVC.userName = user.mnz_fullName; + contactDetailsVC.userHandle = user.handle; + [navigationController pushViewController:contactsVC animated:NO]; + [navigationController pushViewController:contactDetailsVC animated:YES]; + } + break; + } + + case MEGAUserAlertTypeNewShare: + case MEGAUserAlertTypeDeletedShare: + case MEGAUserAlertTypeNewShareNodes: + case MEGAUserAlertTypeRemovedSharesNodes: + case MEGAUserAlertTypeTakedown: + case MEGAUserAlertTypeTakedownReinstated: { + if (node) { + [node navigateToParentAndPresent]; + } + break; + } + + case MEGAUserAlertTypePaymentSucceeded: + case MEGAUserAlertTypePaymentFailed: + case MEGAUserAlertTypePaymentReminder: + break; + + default: + break; + } +} + +#pragma mark - DZNEmptyDataSetSource + +- (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView { + NSString *text = @""; + if ([MEGAReachabilityManager isReachable]) { + text = AMLocalizedString(@"No notifications", @"There are no notifications to display."); + } else { + text = AMLocalizedString(@"noInternetConnection", @"No Internet Connection"); + } + return [[NSAttributedString alloc] initWithString:text attributes:[Helper titleAttributesForEmptyState]]; +} + +- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView { + UIImage *image; + if ([MEGAReachabilityManager isReachable]) { + image = [UIImage imageNamed:@"notificationsEmptyState"]; + } else { + image = [UIImage imageNamed:@"noInternetEmptyState"]; + } + return image; +} + +- (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView { + return UIColor.whiteColor; +} + +- (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView { + return [Helper spaceHeightForEmptyState]; +} + +- (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView { + return [Helper verticalOffsetForEmptyStateWithNavigationBarSize:self.navigationController.navigationBar.frame.size searchBarActive:NO]; +} + +#pragma mark - MEGAGlobalDelegate + +- (void)onUserAlertsUpdate:(MEGASdk *)api userAlertList:(MEGAUserAlertList *)userAlertList { + self.userAlertsArray = api.userAlertList.mnz_userAlertsArray; + [self.tableView reloadData]; +} + +@end diff --git a/iMEGA/My Account/Upgrade/MEGAPurchase.m b/iMEGA/My Account/Upgrade/MEGAPurchase.m index 25fb794f4c..1fd1d212db 100644 --- a/iMEGA/My Account/Upgrade/MEGAPurchase.m +++ b/iMEGA/My Account/Upgrade/MEGAPurchase.m @@ -1,5 +1,7 @@ #import "MEGAPurchase.h" + +#import "DTConstants.h" #import "SVProgressHUD.h" #import "UIApplication+MNZCategory.h" @@ -53,14 +55,14 @@ - (void)purchaseProduct:(SKProduct *)product { MEGALogWarning(@"[StoreKit] In-App purchases is disabled"); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"appPurchaseDisabled", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } else { MEGALogWarning(@"[StoreKit] Product \"%@\" not found", product.productIdentifier); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:AMLocalizedString(@"productNotFound", nil), product.productIdentifier] message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } @@ -73,7 +75,7 @@ - (void)restorePurchase { MEGALogWarning(@"[StoreKit] In-App purchases is disabled"); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"allowPurchase_title", nil) message:AMLocalizedString(@"allowPurchase_message", nil) preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; + [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } @@ -117,7 +119,14 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran case SKPaymentTransactionStatePurchased: MEGALogDebug(@"[StoreKit] Date: %@\nIdentifier: %@\n\t-Original Date: %@\n\t-Original Identifier: %@", transaction.transactionDate, transaction.transactionIdentifier, transaction.originalTransaction.transactionDate, transaction.originalTransaction.transactionIdentifier); - [[MEGASdkManager sharedMEGASdk] submitPurchase:MEGAPaymentMethodItunes receipt:[receiptData base64EncodedStringWithOptions:0] delegate:self]; + + NSTimeInterval lastPublicTimestampAccessed = [[NSUserDefaults standardUserDefaults] doubleForKey:@"kLastPublicTimestampAccessed"]; + if ([NSDate date].timeIntervalSince1970 - lastPublicTimestampAccessed <= SECONDS_IN_DAY) { + uint64_t lastPublicHandleAccessed = [[[NSUserDefaults standardUserDefaults] objectForKey:@"kLastPublicHandleAccessed"] unsignedLongLongValue]; + [[MEGASdkManager sharedMEGASdk] submitPurchase:MEGAPaymentMethodItunes receipt:[receiptData base64EncodedStringWithOptions:0] lastPublicHandle:lastPublicHandleAccessed delegate:self]; + } else { + [[MEGASdkManager sharedMEGASdk] submitPurchase:MEGAPaymentMethodItunes receipt:[receiptData base64EncodedStringWithOptions:0] delegate:self]; + } [_delegate successfulPurchase:self restored:NO]; diff --git a/iMEGA/My Account/Upgrade/ProductDetailViewController.m b/iMEGA/My Account/Upgrade/ProductDetailViewController.m index ee045cbb34..7836002be1 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailViewController.m +++ b/iMEGA/My Account/Upgrade/ProductDetailViewController.m @@ -1,10 +1,10 @@ #import "ProductDetailViewController.h" -#import "ProductDetailTableViewCell.h" - #import "MEGANavigationController.h" #import "MEGAPurchase.h" +#import "ProductDetailTableViewCell.h" +#import "UIApplication+MNZCategory.h" @interface ProductDetailViewController () { BOOL isPurchased; @@ -154,10 +154,11 @@ - (void)successfulPurchase:(MEGAPurchase *)megaPurchase restored:(BOOL)isRestore if (isRestore) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"thankYou_title", nil) message:AMLocalizedString(@"purchaseRestore_message", nil) preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - if ([[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentedViewController] != nil) { - [[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentedViewController] dismissViewControllerAnimated:YES completion:nil]; + if (UIApplication.mnz_presentingViewController) { + [UIApplication.mnz_presentingViewController dismissViewControllerAnimated:YES completion:nil]; } }]]; + [self presentViewController:alertController animated:YES completion:nil]; } else { if (self.presentingViewController) { diff --git a/iMEGA/My Account/Upgrade/UpgradeTableViewController.m b/iMEGA/My Account/Upgrade/UpgradeTableViewController.m index baaa2a37e0..62cee45ca3 100644 --- a/iMEGA/My Account/Upgrade/UpgradeTableViewController.m +++ b/iMEGA/My Account/Upgrade/UpgradeTableViewController.m @@ -6,8 +6,8 @@ #import "MEGASdk+MNZCategory.h" #import "NSString+MNZCategory.h" +#import "NSURL+MNZCategory.h" -#import "Helper.h" #import "MEGAPurchase.h" #import "MEGASdkManager.h" #import "MEGAReachabilityManager.h" @@ -337,19 +337,11 @@ - (void)setupToolbar { } - (void)showTermsOfService { - if ([MEGAReachabilityManager isReachableHUDIfNot]) { - [self showURL:@"https://mega.nz/terms"]; - } + [[NSURL URLWithString:@"https://mega.nz/terms"] mnz_presentSafariViewController]; } - (void)showPrivacyPolicy { - if ([MEGAReachabilityManager isReachableHUDIfNot]) { - [self showURL:@"https://mega.nz/privacy"]; - } -} - -- (void)showURL:(NSString *)urlString { - [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:urlString]]; + [[NSURL URLWithString:@"https://mega.nz/privacy"] mnz_presentSafariViewController]; } - (NSString *)storageAndUnitsByProduct:(SKProduct *)product { @@ -489,8 +481,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath productDetailVC.yearlyProduct = yearlyProduct; [self.navigationController pushViewController:productDetailVC animated:YES]; - [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; } diff --git a/iMEGA/Offline/Offline.storyboard b/iMEGA/Offline/Offline.storyboard index 820220b81c..366351ad88 100644 --- a/iMEGA/Offline/Offline.storyboard +++ b/iMEGA/Offline/Offline.storyboard @@ -1,11 +1,11 @@ - + - + @@ -26,26 +26,107 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - + + @@ -54,133 +135,229 @@ - - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + - - - - + + + + - - + + - - - - + + + + - + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - - + + - + @@ -204,12 +381,13 @@ + + - diff --git a/iMEGA/Offline/OfflineCollectionViewController.h b/iMEGA/Offline/OfflineCollectionViewController.h new file mode 100644 index 0000000000..837e4f9a98 --- /dev/null +++ b/iMEGA/Offline/OfflineCollectionViewController.h @@ -0,0 +1,18 @@ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class OfflineViewController; +@interface OfflineCollectionViewController : UIViewController + +@property (nonatomic, strong) OfflineViewController *offline; + +@property (weak, nonatomic) IBOutlet UICollectionView *collectionView; + +- (void)setCollectionViewEditing:(BOOL)editing animated:(BOOL)animated; +- (void)collectionViewSelectIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Offline/OfflineCollectionViewController.m b/iMEGA/Offline/OfflineCollectionViewController.m new file mode 100644 index 0000000000..98a34aecaf --- /dev/null +++ b/iMEGA/Offline/OfflineCollectionViewController.m @@ -0,0 +1,277 @@ + +#import "OfflineCollectionViewController.h" + +#import "NSString+MNZCategory.h" +#import "UIImageView+MNZCategory.h" + +#import "Helper.h" +#import "MEGAStore.h" + +#import "NodeCollectionViewCell.h" +#import "OfflineViewController.h" + +static NSString *kFileName = @"kFileName"; +static NSString *kPath = @"kPath"; + +@interface OfflineCollectionViewController () + +@property (weak, nonatomic) IBOutlet UIView *searchView; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *collectionViewTopConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchViewTopConstraint; + +@property (assign, nonatomic, getter=isSearchViewVisible) BOOL searchViewVisible; + +@end + +@implementation OfflineCollectionViewController + +#pragma mark - Lifecycle + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.searchView addSubview:self.offline.searchController.searchBar]; + self.offline.searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; +} + +#pragma mark - Public + +- (void)setCollectionViewEditing:(BOOL)editing animated:(BOOL)animated { + self.collectionView.allowsMultipleSelection = editing; + [self.offline setViewEditing:editing]; + + for (NodeCollectionViewCell *cell in self.collectionView.visibleCells) { + cell.selectImageView.hidden = !editing; + cell.selectImageView.image = [UIImage imageNamed:@"checkBoxUnselected"]; + } +} + +- (void)collectionViewSelectIndexPath:(NSIndexPath *)indexPath { + [self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + NSInteger rows = self.offline.searchController.isActive ? self.offline.searchItemsArray.count : self.offline.offlineSortedItems.count; + [self.offline enableButtonsByNumberOfItems]; + return rows; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + + NodeCollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"NodeCollectionID" forIndexPath:indexPath]; + + NSString *directoryPathString = [self.offline currentOfflinePath]; + NSString *nameString = [[self.offline itemAtIndexPath:indexPath] objectForKey:kFileName]; + NSString *pathForItem = [directoryPathString stringByAppendingPathComponent:nameString]; + + cell.nameLabel.text = nameString; + + MOOfflineNode *offNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:pathForItem]]; + NSString *handleString = [offNode base64Handle]; + + cell.thumbnailPlayImageView.hidden = YES; + + BOOL isDirectory; + [[NSFileManager defaultManager] fileExistsAtPath:pathForItem isDirectory:&isDirectory]; + if (isDirectory) { + cell.thumbnailImageView.image = [Helper folderImage]; + } else { + NSString *extension = nameString.pathExtension.lowercaseString; + + if (!handleString) { + NSString *fpLocal = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:pathForItem]; + if (fpLocal) { + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForFingerprint:fpLocal]; + if (node) { + handleString = node.base64Handle; + [[MEGAStore shareInstance] insertOfflineNode:node api:[MEGASdkManager sharedMEGASdk] path:[[Helper pathRelativeToOfflineDirectory:pathForItem] decomposedStringWithCanonicalMapping]]; + } + } + } + + NSString *thumbnailFilePath = [Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"]; + thumbnailFilePath = [thumbnailFilePath stringByAppendingPathComponent:handleString]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath] && handleString) { + UIImage *thumbnailImage = [UIImage imageWithContentsOfFile:thumbnailFilePath]; + if (thumbnailImage) { + cell.thumbnailImageView.image = thumbnailImage; + if (nameString.mnz_isVideoPathExtension) { + cell.thumbnailPlayImageView.hidden = NO; + } + } + + } else { + if (nameString.mnz_isImagePathExtension) { + if (![[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]) { + [[MEGASdkManager sharedMEGASdk] createThumbnail:pathForItem destinatioPath:thumbnailFilePath]; + } + } else { + [cell.thumbnailImageView mnz_setImageForExtension:extension]; + } + } + + } + cell.nameLabel.text = [[MEGASdkManager sharedMEGASdk] unescapeFsIncompatible:nameString]; + + if (self.collectionView.allowsMultipleSelection) { + cell.selectImageView.hidden = NO; + BOOL selected = NO; + for (NSURL *url in self.offline.selectedItems) { + if ([url.path isEqualToString:pathForItem]) { + selected = YES; + } + } + [cell selectCell:selected]; + } else { + cell.selectImageView.hidden = YES; + } + + if (@available(iOS 11.0, *)) { + cell.thumbnailImageView.accessibilityIgnoresInvertColors = YES; + cell.thumbnailPlayImageView.accessibilityIgnoresInvertColors = YES; + } + + return cell; +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + if (collectionView.allowsMultipleSelection) { + NSURL *filePathURL = [[self.offline itemAtIndexPath:indexPath] objectForKey:kPath]; + [self.offline.selectedItems addObject:filePathURL]; + + [self.offline updateNavigationBarTitle]; + [self.offline enableButtonsBySelectedItems]; + + self.offline.allItemsSelected = (self.offline.selectedItems.count == self.offline.offlineSortedItems.count); + + NodeCollectionViewCell *cell = (NodeCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; + [cell selectCell:YES]; + + return; + } + + NodeCollectionViewCell *cell = (NodeCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath]; + [self.offline itemTapped:cell.nameLabel.text atIndexPath:indexPath]; +} + +- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { + if (collectionView.allowsMultipleSelection) { + NSURL *filePathURL = [[self.offline itemAtIndexPath:indexPath] objectForKey:kPath]; + + NSMutableArray *tempArray = self.offline.selectedItems.copy; + for (NSURL *url in tempArray) { + if ([url.filePathURL isEqual:filePathURL]) { + [self.offline.selectedItems removeObject:url]; + } + } + + [self.offline updateNavigationBarTitle]; + [self.offline enableButtonsBySelectedItems]; + + self.offline.allItemsSelected = NO; + + NodeCollectionViewCell *cell = (NodeCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; + [cell selectCell:NO]; + + return; + } +} + +#pragma mark - UIScrolViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + if (self.isSearchViewVisible) { + self.searchViewTopConstraint.constant = - scrollView.contentOffset.y; + if (scrollView.contentOffset.y > 50 && !self.offline.searchController.isActive) { //hide search view when collection offset up is higher than search view height + self.searchViewVisible = NO; + self.collectionViewTopConstraint.constant = 0; + } + } else { + if (scrollView.contentOffset.y < 0) { //keep the search view next to collection view offset when scroll down + self.searchViewTopConstraint.constant = - scrollView.contentOffset.y - 50; + } + } + + if (self.offline.searchController.isActive) { + [self.offline.searchController.searchBar resignFirstResponder]; + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { + if (scrollView.contentOffset.y < -50) { //show search view when collection offset down is higher than search view height + self.searchViewVisible = YES; + [UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ + self.collectionViewTopConstraint.constant = 50; + self.collectionView.contentOffset = CGPointMake(0, 0); + + [self.view layoutIfNeeded]; + } completion:nil]; + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + if (self.searchViewVisible) { + if (scrollView.contentOffset.y > 0) { + if (scrollView.contentOffset.y < 20) { //simulate that search bar is inside collection view and offset items to show search bar and items at top of scroll + [UIView animateWithDuration:.2 animations:^{ + self.collectionView.contentOffset = CGPointMake(0, 0); + [self.view layoutIfNeeded]; + }]; + } else if (scrollView.contentOffset.y < 50) { //hide search bar when offset collection up between 20 and 50 points of the search view + self.searchViewVisible = NO; + [UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ + self.collectionViewTopConstraint.constant = 0; + self.collectionView.contentOffset = CGPointMake(0, 0); + self.searchViewTopConstraint.constant = -50; + [self.view layoutIfNeeded]; + } completion:nil]; + } + } + } else { + if (scrollView.contentOffset.y < 0) { //show search bar when drag collection view down + self.searchViewVisible = YES; + self.searchViewTopConstraint.constant = 0 - scrollView.contentOffset.y; + [UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ + self.collectionViewTopConstraint.constant = 50; + [self.view layoutIfNeeded]; + } completion:nil]; + } + } +} + +#pragma mark - Actions + +- (IBAction)infoTouchUpInside:(UIButton *)sender { + if (self.collectionView.allowsMultipleSelection) { + return; + } + + CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.collectionView]; + NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:buttonPosition]; + + UIAlertController *infoAlertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + [infoAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + + UIAlertAction *removeItemAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"remove", @"Title for the action that allows to remove a file or folder") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + NodeCollectionViewCell *cell = (NodeCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; + NSString *itemPath = [[self.offline currentOfflinePath] stringByAppendingPathComponent:cell.nameLabel.text]; + [self.offline removeOfflineNodeCell:itemPath]; + }]; + [removeItemAction setValue:[UIColor mnz_black333333] forKey:@"titleTextColor"]; + [infoAlertController addAction:removeItemAction]; + + if ([[UIDevice currentDevice] iPadDevice]) { + infoAlertController.modalPresentationStyle = UIModalPresentationPopover; + infoAlertController.popoverPresentationController.sourceView = sender; + infoAlertController.popoverPresentationController.sourceRect = CGRectMake(0, 0, sender.frame.size.width/2, sender.frame.size.height/2); + } + + [self presentViewController:infoAlertController animated:YES completion:nil]; +} + +@end diff --git a/iMEGA/Offline/OfflineTableViewViewController.h b/iMEGA/Offline/OfflineTableViewViewController.h new file mode 100644 index 0000000000..cd0ab75757 --- /dev/null +++ b/iMEGA/Offline/OfflineTableViewViewController.h @@ -0,0 +1,18 @@ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class OfflineViewController; +@interface OfflineTableViewViewController : UIViewController + +@property (nonatomic, strong) OfflineViewController *offline; + +@property (weak, nonatomic) IBOutlet UITableView *tableView; + +- (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated; +- (void)tableViewSelectIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Offline/OfflineTableViewViewController.m b/iMEGA/Offline/OfflineTableViewViewController.m new file mode 100644 index 0000000000..e1750e96c1 --- /dev/null +++ b/iMEGA/Offline/OfflineTableViewViewController.m @@ -0,0 +1,266 @@ + +#import "OfflineTableViewViewController.h" + +#import "NSString+MNZCategory.h" +#import "UIImageView+MNZCategory.h" + +#import "Helper.h" +#import "MEGAStore.h" + +#import "OfflineTableViewCell.h" +#import "OfflineViewController.h" + +static NSString *kFileName = @"kFileName"; +static NSString *kPath = @"kPath"; + +@interface OfflineTableViewViewController () + +@end + +@implementation OfflineTableViewViewController + +#pragma mark - Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(self.offline.searchController.searchBar.frame)); + self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +#pragma mark - Public + +- (void)tableViewSelectIndexPath:(NSIndexPath *)indexPath { + [self tableView:self.tableView didSelectRowAtIndexPath:indexPath]; +} + +- (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { + [self.tableView setEditing:editing animated:animated]; + + [self.offline setViewEditing:editing]; + + if (editing) { + for (OfflineTableViewCell *cell in self.tableView.visibleCells) { + UIView *view = [[UIView alloc] init]; + view.backgroundColor = UIColor.clearColor; + cell.selectedBackgroundView = view; + } + } else { + for (OfflineTableViewCell *cell in self.tableView.visibleCells) { + cell.selectedBackgroundView = nil; + } + } +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSInteger rows = self.offline.searchController.isActive ? self.offline.searchItemsArray.count : self.offline.offlineSortedItems.count; + [self.offline enableButtonsByNumberOfItems]; + return rows; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + OfflineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"offlineTableViewCell" forIndexPath:indexPath]; + + NSString *directoryPathString = [self.offline currentOfflinePath]; + NSString *nameString = [[self.offline itemAtIndexPath:indexPath] objectForKey:kFileName]; + NSString *pathForItem = [directoryPathString stringByAppendingPathComponent:nameString]; + + cell.itemNameString = nameString; + + MOOfflineNode *offNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:pathForItem]]; + NSString *handleString = offNode.base64Handle; + + cell.thumbnailPlayImageView.hidden = YES; + + BOOL isDirectory; + [[NSFileManager defaultManager] fileExistsAtPath:pathForItem isDirectory:&isDirectory]; + if (isDirectory) { + cell.thumbnailImageView.image = [Helper folderImage]; + + NSInteger files = 0; + NSInteger folders = 0; + + NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pathForItem error:nil]; + for (NSString *file in directoryContents) { + BOOL isDirectory; + NSString *path = [pathForItem stringByAppendingPathComponent:file]; + if (![path.pathExtension.lowercaseString isEqualToString:@"mega"]) { + [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; + isDirectory ? folders++ : files++; + } + + } + + cell.infoLabel.text = [NSString mnz_stringByFiles:files andFolders:folders]; + } else { + NSString *extension = nameString.pathExtension.lowercaseString; + + if (!handleString) { + NSString *fpLocal = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:pathForItem]; + if (fpLocal) { + MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForFingerprint:fpLocal]; + if (node) { + handleString = node.base64Handle; + [[MEGAStore shareInstance] insertOfflineNode:node api:[MEGASdkManager sharedMEGASdk] path:[[Helper pathRelativeToOfflineDirectory:pathForItem] decomposedStringWithCanonicalMapping]]; + } + } + } + + NSString *thumbnailFilePath = [Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"]; + thumbnailFilePath = [thumbnailFilePath stringByAppendingPathComponent:handleString]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath] && handleString) { + UIImage *thumbnailImage = [UIImage imageWithContentsOfFile:thumbnailFilePath]; + if (thumbnailImage) { + [cell.thumbnailImageView setImage:thumbnailImage]; + if (nameString.mnz_isVideoPathExtension) { + cell.thumbnailPlayImageView.hidden = NO; + } + } + + } else { + if (nameString.mnz_isImagePathExtension) { + if (![[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]) { + [[MEGASdkManager sharedMEGASdk] createThumbnail:pathForItem destinatioPath:thumbnailFilePath]; + } + } else { + [cell.thumbnailImageView mnz_setImageForExtension:extension]; + } + } + + NSDictionary *filePropertiesDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:pathForItem error:nil]; + + time_t rawtime = [[filePropertiesDictionary valueForKey:NSFileModificationDate] timeIntervalSince1970]; + NSString *date = [Helper dateWithISO8601FormatOfRawTime:rawtime]; + + unsigned long long size; + size = [[[NSFileManager defaultManager] attributesOfItemAtPath:pathForItem error:nil] fileSize]; + + NSString *sizeString = [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleMemory]; + NSString *sizeAndDate = [NSString stringWithFormat:@"%@ • %@", sizeString, date]; + cell.infoLabel.text = sizeAndDate; + } + cell.nameLabel.text = [[MEGASdkManager sharedMEGASdk] unescapeFsIncompatible:nameString]; + + if (self.tableView.isEditing) { + for (NSURL *url in self.offline.selectedItems) { + if ([url.path isEqualToString:pathForItem]) { + [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; + } + } + + UIView *view = [[UIView alloc] init]; + view.backgroundColor = UIColor.clearColor; + cell.selectedBackgroundView = view; + } + + if (@available(iOS 11.0, *)) { + cell.thumbnailImageView.accessibilityIgnoresInvertColors = YES; + cell.thumbnailPlayImageView.accessibilityIgnoresInvertColors = YES; + } else { + cell.delegate = self; + } + + return cell; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + + if (tableView.isEditing) { + NSURL *filePathURL = [[self.offline itemAtIndexPath:indexPath] objectForKey:kPath]; + [self.offline.selectedItems addObject:filePathURL]; + + [self.offline updateNavigationBarTitle]; + [self.offline enableButtonsBySelectedItems]; + + self.offline.allItemsSelected = (self.offline.selectedItems.count == self.offline.offlineSortedItems.count); + + return; + } + + OfflineTableViewCell *cell = (OfflineTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]; + [self.offline itemTapped:cell.nameLabel.text atIndexPath:indexPath]; +} + +- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath { + + if (tableView.isEditing) { + NSURL *filePathURL = [[self.offline itemAtIndexPath:indexPath] objectForKey:kPath]; + + NSMutableArray *tempArray = self.offline.selectedItems.copy; + for (NSURL *url in tempArray) { + if ([url.filePathURL isEqual:filePathURL]) { + [self.offline.selectedItems removeObject:url]; + } + } + + [self.offline updateNavigationBarTitle]; + [self.offline enableButtonsBySelectedItems]; + + self.offline.allItemsSelected = NO; + + return; + } +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + +- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { + UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"Share" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { + OfflineTableViewCell *cell = (OfflineTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]; + NSString *itemPath = [[self.offline currentOfflinePath] stringByAppendingPathComponent:cell.itemNameString]; + [self.offline removeOfflineNodeCell:itemPath]; + }]; + deleteAction.image = [UIImage imageNamed:@"delete"]; + deleteAction.backgroundColor = [UIColor colorWithRed:0.94 green:0.22 blue:0.23 alpha:1]; + return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]]; +} + +#pragma clang diagnostic pop + +#pragma mark - MGSwipeTableCellDelegate + +- (BOOL)swipeTableCell:(MGSwipeTableCell *)cell canSwipe:(MGSwipeDirection)direction fromPoint:(CGPoint)point { + if (self.tableView.isEditing) { + return NO; + } + + if (direction == MGSwipeDirectionLeftToRight) { + return NO; + } + + return YES; +} + +- (NSArray *)swipeTableCell:(MGSwipeTableCell *)cell swipeButtonsForDirection:(MGSwipeDirection)direction swipeSettings:(MGSwipeSettings *)swipeSettings expansionSettings:(MGSwipeExpansionSettings *)expansionSettings { + + swipeSettings.transition = MGSwipeTransitionDrag; + expansionSettings.buttonIndex = 0; + expansionSettings.expansionLayout = MGSwipeExpansionLayoutCenter; + expansionSettings.fillOnTrigger = NO; + expansionSettings.threshold = 2; + + if (direction == MGSwipeDirectionRightToLeft) { + + MGSwipeButton *deleteButton = [MGSwipeButton buttonWithTitle:@"" icon:[UIImage imageNamed:@"delete"] backgroundColor:[UIColor colorWithRed:0.93 green:0.22 blue:0.23 alpha:1.0] padding:25 callback:^BOOL(MGSwipeTableCell *sender) { + OfflineTableViewCell *offlineCell = (OfflineTableViewCell *)cell; + NSString *itemPath = [self.offline.currentOfflinePath stringByAppendingPathComponent:offlineCell.itemNameString]; + [self.offline removeOfflineNodeCell:itemPath]; + + return YES; + }]; + [deleteButton iconTintColor:[UIColor whiteColor]]; + + return @[deleteButton]; + } + else { + return nil; + } +} + +@end diff --git a/iMEGA/Offline/OfflineViewController.h b/iMEGA/Offline/OfflineViewController.h index 455980c985..72871b9ada 100644 --- a/iMEGA/Offline/OfflineViewController.h +++ b/iMEGA/Offline/OfflineViewController.h @@ -1,7 +1,26 @@ #import +@class MEGAQLPreviewController; + @interface OfflineViewController : UIViewController @property (nonatomic, strong) NSIndexPath *peekIndexPath; +@property (assign, nonatomic) BOOL allItemsSelected; +@property (strong, nonatomic) NSString *previewDocumentPath; + +@property (nonatomic, strong) NSMutableArray *selectedItems; +@property (nonatomic, strong) NSMutableArray *offlineSortedItems; +@property (nonatomic) NSMutableArray *searchItemsArray; +@property (nonatomic) UISearchController *searchController; + +- (NSString *)currentOfflinePath; +- (NSDictionary *)itemAtIndexPath:(NSIndexPath *)indexPath; +- (void)updateNavigationBarTitle; +- (BOOL)removeOfflineNodeCell:(NSString *)itemPath; +- (void)setViewEditing:(BOOL)editing; +- (void)itemTapped:(NSString *)name atIndexPath:(NSIndexPath *)indexPath; +- (void)enableButtonsByNumberOfItems; +- (void)enableButtonsBySelectedItems; + @end diff --git a/iMEGA/Offline/OfflineViewController.m b/iMEGA/Offline/OfflineViewController.m index 3f0e67c445..add4f7c7cb 100644 --- a/iMEGA/Offline/OfflineViewController.m +++ b/iMEGA/Offline/OfflineViewController.m @@ -1,22 +1,26 @@ #import "OfflineViewController.h" #import "SVProgressHUD.h" -#import "UIScrollView+EmptyDataSet.h" +#import "UIScrollView+EmptyDataSet.h" #import "NSString+MNZCategory.h" +#import "NSFileManager+MNZCategory.h" + #import "MEGANavigationController.h" #import "MEGASdkManager.h" #import "PreviewDocumentViewController.h" #import "Helper.h" #import "MEGAReachabilityManager.h" -#import "OfflineTableViewCell.h" #import "OpenInActivity.h" #import "SortByTableViewController.h" -#import "UIImageView+MNZCategory.h" - #import "MEGAStore.h" #import "MEGAAVViewController.h" #import "MEGAQLPreviewController.h" +#import "OfflineTableViewViewController.h" +#import "OfflineCollectionViewController.h" +#import "LayoutView.h" +#import "NodeCollectionViewCell.h" +#import "OfflineTableViewCell.h" static NSString *kFileName = @"kFileName"; static NSString *kIndex = @"kIndex"; @@ -25,23 +29,10 @@ static NSString *kFileSize = @"kFileSize"; static NSString *kisDirectory = @"kisDirectory"; -@interface OfflineViewController () { - NSString *previewDocumentPath; - BOOL allItemsSelected; +@interface OfflineViewController () { } -@property (weak, nonatomic) IBOutlet UITableView *tableView; - -@property (nonatomic) id previewingContext; - -@property (nonatomic, strong) NSMutableArray *offlineSortedItems; -@property (nonatomic, strong) NSMutableArray *offlineFiles; -@property (nonatomic, strong) NSMutableArray *offlineMultimediaFiles; -@property (nonatomic, strong) NSMutableArray *offlineItems; - -@property (nonatomic, strong) NSString *folderPathFromOffline; - -@property (nonatomic, strong) NSMutableArray *selectedItems; +@property (weak, nonatomic) IBOutlet UIView *containerView; @property (weak, nonatomic) IBOutlet UIBarButtonItem *selectAllBarButtonItem; @property (weak, nonatomic) IBOutlet UIBarButtonItem *moreBarButtonItem; @@ -51,11 +42,18 @@ @interface OfflineViewController () previewingContext; + +@property (nonatomic, strong) NSMutableArray *offlineMultimediaFiles; +@property (nonatomic, strong) NSMutableArray *offlineItems; +@property (nonatomic, strong) NSMutableArray *offlineFiles; +@property (nonatomic, strong) NSString *folderPathFromOffline; @property (strong, nonatomic) UIDocumentInteractionController *documentInteractionController; -@property (nonatomic) NSMutableArray *searchItemsArray; -@property (nonatomic) UISearchController *searchController; +@property (nonatomic, strong) OfflineTableViewViewController *offlineTableView; +@property (nonatomic, strong) OfflineCollectionViewController *offlineCollectionView; +@property (nonatomic, assign) LayoutMode layoutView; @end @@ -65,15 +63,13 @@ @implementation OfflineViewController - (void)viewDidLoad { [super viewDidLoad]; - - self.tableView.emptyDataSetSource = self; - self.tableView.emptyDataSetDelegate = self; + + [self determineLayoutView]; if (self.folderPathFromOffline == nil) { [self.navigationItem setTitle:AMLocalizedString(@"offline", @"Offline")]; } else { - NSString *currentFolderName = [self.folderPathFromOffline lastPathComponent]; - [self.navigationItem setTitle:currentFolderName]; + [self.navigationItem setTitle:self.folderPathFromOffline.lastPathComponent]; } UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; @@ -84,20 +80,16 @@ - (void)viewDidLoad { self.editBarButtonItem.title = AMLocalizedString(@"edit", @"Caption of a button to edit the files that are selected"); self.navigationItem.rightBarButtonItem = self.moreBarButtonItem; - self.searchController = [Helper customSearchControllerWithSearchResultsUpdaterDelegate:self searchBarDelegate:self]; - [self.tableView setContentOffset:CGPointMake(0, CGRectGetHeight(self.searchController.searchBar.frame))]; self.definesPresentationContext = YES; [self.view addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]]; - - self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [[NSNotificationCenter defaultCenter] addObserver:self.tableView selector:@selector(reloadEmptyDataSet) name:kReachabilityChangedNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self.offlineTableView.tableView selector:@selector(reloadEmptyDataSet) name:kReachabilityChangedNotification object:nil]; + [[MEGASdkManager sharedMEGASdk] addMEGATransferDelegate:self]; [[MEGASdkManager sharedMEGASdkFolder] addMEGATransferDelegate:self]; [[MEGAReachabilityManager sharedManager] retryPendingConnections]; @@ -111,33 +103,29 @@ - (void)viewWillAppear:(BOOL)animated { NSString *documentProviderLog = @"MEGAiOS.docExt.log"; NSString *fileProviderLog = @"MEGAiOS.fileExt.log"; NSString *shareExtensionLog = @"MEGAiOS.shareExt.log"; - [fileManager removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:documentProviderLog] error:nil]; + [fileManager mnz_removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:documentProviderLog]]; [fileManager copyItemAtPath:[logsPath stringByAppendingPathComponent:documentProviderLog] toPath:[[self currentOfflinePath] stringByAppendingPathComponent:documentProviderLog] error:nil]; - [fileManager removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:fileProviderLog] error:nil]; + [fileManager mnz_removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:fileProviderLog]]; [fileManager copyItemAtPath:[logsPath stringByAppendingPathComponent:fileProviderLog] toPath:[[self currentOfflinePath] stringByAppendingPathComponent:fileProviderLog] error:nil]; - [fileManager removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:shareExtensionLog] error:nil]; + [fileManager mnz_removeItemAtPath:[[self currentOfflinePath] stringByAppendingPathComponent:shareExtensionLog]]; [fileManager copyItemAtPath:[logsPath stringByAppendingPathComponent:shareExtensionLog] toPath:[[self currentOfflinePath] stringByAppendingPathComponent:shareExtensionLog] error:nil]; } } - if (!self.tableView.tableHeaderView) { - self.tableView.tableHeaderView = self.searchController.searchBar; - } - [self reloadUI]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - [[NSNotificationCenter defaultCenter] removeObserver:self.tableView name:kReachabilityChangedNotification object:nil]; - + [[NSNotificationCenter defaultCenter] removeObserver:self.offlineTableView.tableView name:kReachabilityChangedNotification object:nil]; + [[MEGASdkManager sharedMEGASdk] removeMEGATransferDelegate:self]; [[MEGASdkManager sharedMEGASdkFolder] removeMEGATransferDelegate:self]; - if (self.tableView.isEditing) { + if (self.offlineTableView.tableView.isEditing) { self.selectedItems = nil; - [self setTableViewEditing:NO animated:NO]; + [self setEditMode:NO]; } } @@ -149,7 +137,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id context) { - [self.tableView reloadEmptyDataSet]; + [self.offlineTableView.tableView reloadEmptyDataSet]; } completion:nil]; } @@ -184,6 +172,121 @@ - (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withT }]; } +#pragma mark - Layout + + +- (void)determineLayoutView { + + NSString *relativePath = [[self currentOfflinePath] stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%@/Documents/", NSHomeDirectory()] withString:@""]; + MOOfflineFolderLayout *folderLayout = [[MEGAStore shareInstance] fetchOfflineFolderLayoutWithPath:relativePath]; + + if (folderLayout) { + switch (folderLayout.value.integerValue) { + case 0: + [self initTable]; + break; + + case 1: + [self initCollection]; + break; + + default: + [self initTable]; + break; + } + } else { + NSInteger nodesWithThumbnail = 0; + NSInteger nodesWithoutThumbnail = 0; + + NSString *directoryPathString = [self currentOfflinePath]; + NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPathString error:NULL]; + + for (int i = 0; i < directoryContents.count; i++) { + NSString *fileName = [directoryContents objectAtIndex:i]; + NSString *pathForItem = [directoryPathString stringByAppendingPathComponent:fileName]; + + MOOfflineNode *offNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:pathForItem]]; + NSString *handleString = offNode.base64Handle; + + BOOL isDirectory; + [[NSFileManager defaultManager] fileExistsAtPath:pathForItem isDirectory:&isDirectory]; + if (isDirectory) { + nodesWithoutThumbnail = nodesWithoutThumbnail + 1; + } else { + NSString *thumbnailFilePath = [Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"]; + thumbnailFilePath = [thumbnailFilePath stringByAppendingPathComponent:handleString]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath] && handleString) { + nodesWithThumbnail = nodesWithThumbnail + 1; + } else { + nodesWithoutThumbnail = nodesWithoutThumbnail + 1; + } + } + } + + if (nodesWithThumbnail > nodesWithoutThumbnail) { + [self initCollection]; + } else { + [self initTable]; + } + } +} + + +- (void)initTable { + [self.offlineCollectionView willMoveToParentViewController:nil]; + [self.offlineCollectionView.view removeFromSuperview]; + [self.offlineCollectionView removeFromParentViewController]; + self.offlineCollectionView = nil; + + self.searchController = [Helper customSearchControllerWithSearchResultsUpdaterDelegate:self searchBarDelegate:self]; + self.layoutView = LayoutModeList; + + self.offlineTableView = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineTableID"]; + [self addChildViewController:self.offlineTableView]; + self.offlineTableView.view.frame = self.containerView.bounds; + [self.containerView addSubview:self.offlineTableView.view]; + [self.offlineTableView didMoveToParentViewController:self]; + + self.offlineTableView.offline = self; + self.offlineTableView.tableView.tableHeaderView = self.searchController.searchBar; + self.offlineTableView.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(self.searchController.searchBar.frame)); + self.offlineTableView.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; + self.offlineTableView.tableView.emptyDataSetDelegate = self; + self.offlineTableView.tableView.emptyDataSetSource = self; +} + +- (void)initCollection { + [self.offlineTableView willMoveToParentViewController:nil]; + [self.offlineTableView.view removeFromSuperview]; + [self.offlineTableView removeFromParentViewController]; + self.offlineTableView = nil; + + self.searchController = [Helper customSearchControllerWithSearchResultsUpdaterDelegate:self searchBarDelegate:self]; + self.layoutView = LayoutModeThumbnail; + + self.offlineCollectionView = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineCollectionID"]; + self.offlineCollectionView.offline = self; + [self addChildViewController:self.offlineCollectionView]; + self.offlineCollectionView.view.frame = self.containerView.bounds; + [self.containerView addSubview:self.offlineCollectionView.view]; + [self.offlineCollectionView didMoveToParentViewController:self]; + + self.offlineCollectionView.collectionView.emptyDataSetDelegate = self; + self.offlineCollectionView.collectionView.emptyDataSetSource = self; +} + +- (void)changeLayoutMode { + if (self.layoutView == LayoutModeList) { + [self initCollection]; + } else { + [self initTable]; + } + + NSString *relativePath = [[self currentOfflinePath] stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%@/Documents/", NSHomeDirectory()] withString:@""]; + [[MEGAStore shareInstance] insertOfflineFolderLayoutWithPOath:relativePath layout:self.layoutView]; +} + #pragma mark - Private - (void)reloadUI { @@ -286,24 +389,16 @@ - (void)reloadUI { } if ([self.offlineSortedItems count] == 0) { - self.tableView.tableHeaderView = nil; + self.offlineTableView.tableView.tableHeaderView = nil; } else { - if (!self.tableView.tableHeaderView) { - self.tableView.tableHeaderView = self.searchController.searchBar; + if (!self.offlineTableView.tableView.tableHeaderView) { + self.offlineTableView.tableView.tableHeaderView = self.searchController.searchBar; } } [self updateNavigationBarTitle]; - [self.tableView reloadData]; -} - -- (NSString *)currentOfflinePath { - NSString *pathString = [Helper pathForOffline]; - if (self.folderPathFromOffline != nil) { - pathString = [pathString stringByAppendingPathComponent:self.folderPathFromOffline]; - } - return pathString; + [self reloadData]; } - (NSString *)folderPathFromOffline:(NSString *)absolutePath folder:(NSString *)folderName { @@ -447,385 +542,65 @@ - (void)sortBySortType:(MEGASortOrderType)sortOrderType { self.offlineItems = [NSMutableArray arrayWithArray:sortedArray]; } -- (NSDictionary *)itemAtIndexPath:(NSIndexPath *)indexPath { - NSDictionary *item = nil; - if (indexPath) { - if (self.searchController.isActive) { - item = [self.searchItemsArray objectAtIndex:indexPath.row]; - } else { - item = [self.offlineSortedItems objectAtIndex:indexPath.row]; - } - } - return item; -} - -- (BOOL)removeOfflineNodeCell:(NSString *)itemPath { - NSArray *offlinePathsOnFolderArray; - MOOfflineNode *offlineNode; - - BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:itemPath isDirectory:&isDirectory]; - if (isDirectory) { - if ([[[[MEGASdkManager sharedMEGASdk] transfers] size] integerValue] != 0) { - [self cancelPendingTransfersOnFolder:itemPath folderLink:NO]; - } - if ([[[[MEGASdkManager sharedMEGASdkFolder] transfers] size] integerValue] != 0) { - [self cancelPendingTransfersOnFolder:itemPath folderLink:YES]; - } - offlinePathsOnFolderArray = [self offlinePathOnFolder:itemPath]; - } - - NSError *error = nil; - BOOL success = [ [NSFileManager defaultManager] removeItemAtPath:itemPath error:&error]; - offlineNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:itemPath]]; - if (!success || error) { - [SVProgressHUD showErrorWithStatus:@""]; - return NO; - } else { - if (isDirectory) { - for (NSString *localPathAux in offlinePathsOnFolderArray) { - offlineNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:localPathAux]; - if (offlineNode) { - [[MEGAStore shareInstance] removeOfflineNode:offlineNode]; - } - } - } else { - if (offlineNode) { - [[MEGAStore shareInstance] removeOfflineNode:offlineNode]; - } - } - [self reloadUI]; - return YES; - } -} - - (MEGAQLPreviewController *)qlPreviewControllerForIndexPath:(NSIndexPath *)indexPath { MEGAQLPreviewController *previewController = [[MEGAQLPreviewController alloc] initWithArrayOfFiles:self.offlineFiles]; NSInteger selectedIndexFile = [[[self.offlineSortedItems objectAtIndex:indexPath.row] objectForKey:kIndex] integerValue]; previewController.currentPreviewItemIndex = selectedIndexFile; - [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; + [self.offlineTableView.tableView deselectRowAtIndexPath:indexPath animated:YES]; return previewController; } -- (void)updateNavigationBarTitle { - NSString *navigationTitle; - if (self.tableView.isEditing) { - if (self.selectedItems.count == 0) { - navigationTitle = AMLocalizedString(@"selectTitle", @"Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos"); - } else { - navigationTitle = (self.selectedItems.count == 1) ? [NSString stringWithFormat:AMLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedItems.count] : [NSString stringWithFormat:AMLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedItems.count]; - } +- (void)reloadData { + if (self.layoutView == LayoutModeList) { + [self.offlineTableView.tableView reloadData]; } else { - if (self.folderPathFromOffline == nil) { - navigationTitle = AMLocalizedString(@"offline", @"Offline"); - } else { - navigationTitle = [self.folderPathFromOffline lastPathComponent]; - } + [self.offlineCollectionView.collectionView reloadData]; } - - self.navigationItem.title = navigationTitle; } -#pragma mark - UITableViewDataSource - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - NSInteger rows = self.searchController.isActive ? self.searchItemsArray.count : self.offlineSortedItems.count; - if (rows == 0) { - self.sortByBarButtonItem.enabled = NO; - [self.editBarButtonItem setEnabled:NO]; +- (void)setEditMode:(BOOL)editMode { + if (self.layoutView == LayoutModeList) { + [self.offlineTableView setTableViewEditing:editMode animated:YES]; } else { - self.sortByBarButtonItem.enabled = YES; - [self.editBarButtonItem setEnabled:YES]; + [self.offlineCollectionView setCollectionViewEditing:editMode animated:YES]; } - return rows; } - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - OfflineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"offlineTableViewCell" forIndexPath:indexPath]; - - NSString *directoryPathString = [self currentOfflinePath]; - NSString *nameString = [[self itemAtIndexPath:indexPath] objectForKey:kFileName]; - NSString *pathForItem = [directoryPathString stringByAppendingPathComponent:nameString]; - - [cell setItemNameString:nameString]; - - MOOfflineNode *offNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:pathForItem]]; - NSString *handleString = [offNode base64Handle]; - - [cell.thumbnailPlayImageView setHidden:YES]; - - BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:pathForItem isDirectory:&isDirectory]; - if (isDirectory) { - [cell.thumbnailImageView setImage:[Helper folderImage]]; - - NSInteger files = 0; - NSInteger folders = 0; - - NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pathForItem error:nil]; - for (NSString *file in directoryContents) { - BOOL isDirectory; - NSString *path = [pathForItem stringByAppendingPathComponent:file]; - if (![path.pathExtension.lowercaseString isEqualToString:@"mega"]) { - [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; - isDirectory ? folders++ : files++; - } - - } - - [cell.infoLabel setText:[NSString mnz_stringByFiles:files andFolders:folders]]; +- (void)selectIndexPath:(NSIndexPath *)indexPath { + if (self.layoutView == LayoutModeList) { + [self.offlineTableView tableViewSelectIndexPath:indexPath]; + [self.offlineTableView.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; } else { - NSString *extension = [[nameString pathExtension] lowercaseString]; - - if (!handleString) { - NSString *fpLocal = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:pathForItem]; - if (fpLocal) { - MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForFingerprint:fpLocal]; - if (node) { - handleString = [node base64Handle]; - [[MEGAStore shareInstance] insertOfflineNode:node api:[MEGASdkManager sharedMEGASdk] path:[[Helper pathRelativeToOfflineDirectory:pathForItem] decomposedStringWithCanonicalMapping]]; - } - } - } - - NSString *thumbnailFilePath = [Helper pathForSharedSandboxCacheDirectory:@"thumbnailsV3"]; - thumbnailFilePath = [thumbnailFilePath stringByAppendingPathComponent:handleString]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath] && handleString) { - UIImage *thumbnailImage = [UIImage imageWithContentsOfFile:thumbnailFilePath]; - if (thumbnailImage != nil) { - [cell.thumbnailImageView setImage:thumbnailImage]; - if (nameString.mnz_isVideoPathExtension) { - [cell.thumbnailPlayImageView setHidden:NO]; - } - } - - } else { - if (nameString.mnz_isImagePathExtension) { - if (![[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]) { - [[MEGASdkManager sharedMEGASdk] createThumbnail:pathForItem destinatioPath:thumbnailFilePath]; - } - } else { - [cell.thumbnailImageView mnz_setImageForExtension:extension]; - } - } - - NSDictionary *filePropertiesDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:pathForItem error:nil]; - - time_t rawtime = [[filePropertiesDictionary valueForKey:NSFileModificationDate] timeIntervalSince1970]; - NSString *date = [Helper dateWithISO8601FormatOfRawTime:rawtime]; - - unsigned long long size; - size = [[[NSFileManager defaultManager] attributesOfItemAtPath:pathForItem error:nil] fileSize]; - - NSString *sizeString = [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleMemory]; - NSString *sizeAndDate = [NSString stringWithFormat:@"%@ • %@", sizeString, date]; - [cell.infoLabel setText:sizeAndDate]; - } - [cell.nameLabel setText:[[MEGASdkManager sharedMEGASdk] unescapeFsIncompatible:nameString]]; - - if (self.tableView.isEditing) { - for (NSURL *url in self.selectedItems) { - if ([url.path isEqualToString:pathForItem]) { - [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; - } - } - - UIView *view = [[UIView alloc] init]; - view.backgroundColor = UIColor.clearColor; - cell.selectedBackgroundView = view; + [self.offlineCollectionView collectionViewSelectIndexPath:indexPath]; + [self.offlineCollectionView.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone]; } - - if (@available(iOS 11.0, *)) { - cell.thumbnailImageView.accessibilityIgnoresInvertColors = YES; - cell.thumbnailPlayImageView.accessibilityIgnoresInvertColors = YES; - } else { - cell.delegate = self; - } - - return cell; } -#pragma mark - UITableViewDelegate - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - - if (tableView.isEditing) { - NSURL *filePathURL = [[self itemAtIndexPath:indexPath] objectForKey:kPath]; - [self.selectedItems addObject:filePathURL]; - - [self updateNavigationBarTitle]; - - if (self.selectedItems.count > 0) { - [self.activityBarButtonItem setEnabled:![self isDirectorySelected]]; - [self.deleteBarButtonItem setEnabled:YES]; - } - - if (self.selectedItems.count == self.offlineSortedItems.count) { - allItemsSelected = YES; - } else { - allItemsSelected = NO; - } - - return; - } - - OfflineTableViewCell *cell = (OfflineTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]; - NSString *itemNameString = [cell itemNameString]; - previewDocumentPath = [[self currentOfflinePath] stringByAppendingPathComponent:itemNameString]; - - BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:previewDocumentPath isDirectory:&isDirectory]; - if (isDirectory) { - NSString *folderPathFromOffline = [self folderPathFromOffline:previewDocumentPath folder:[cell itemNameString]]; - - OfflineViewController *offlineVC = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineViewControllerID"]; - [offlineVC setFolderPathFromOffline:folderPathFromOffline]; - if (self.searchController.isActive) { - [self.searchController dismissViewControllerAnimated:YES completion:^{ - [self.navigationController pushViewController:offlineVC animated:YES]; - }]; - } else { - [self.navigationController pushViewController:offlineVC animated:YES]; - } - } else if (previewDocumentPath.mnz_isMultimediaPathExtension) { - AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:previewDocumentPath]]; - - if (asset.playable) { - MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithURL:[NSURL fileURLWithPath:previewDocumentPath]]; - [self presentViewController:megaAVViewController animated:YES completion:nil]; - } else { - MEGAQLPreviewController *previewController = [self qlPreviewControllerForIndexPath:indexPath]; - [self presentViewController:previewController animated:YES completion:nil]; - } - - } else if ([previewDocumentPath.pathExtension isEqualToString:@"pdf"]){ - MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"previewDocumentNavigationID"]; - PreviewDocumentViewController *previewController = navigationController.viewControllers.firstObject; - previewController.filesPathsArray = self.offlineFiles; - previewController.nodeFileIndex = [[[self itemAtIndexPath:indexPath] objectForKey:kIndex] integerValue]; - [self presentViewController:navigationController animated:YES completion:nil]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; +- (NSInteger)numberOfRows { + NSInteger numberOfRows = 0; + if (self.layoutView == LayoutModeList) { + numberOfRows = [self.offlineTableView.tableView numberOfRowsInSection:0]; } else { - MEGAQLPreviewController *previewController = [self qlPreviewControllerForIndexPath:indexPath]; - [self presentViewController:previewController animated:YES completion:nil]; + numberOfRows = [self.offlineCollectionView.collectionView numberOfItemsInSection:0]; } -} - -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath { - if (tableView.isEditing) { - NSURL *filePathURL = [[self itemAtIndexPath:indexPath] objectForKey:kPath]; - - NSMutableArray *tempArray = [self.selectedItems copy]; - for (NSURL *url in tempArray) { - if ([[url filePathURL] isEqual:filePathURL]) { - [self.selectedItems removeObject:url]; - } - } - - [self updateNavigationBarTitle]; - - if (self.selectedItems.count == 0) { - [self.activityBarButtonItem setEnabled:NO]; - [self.deleteBarButtonItem setEnabled:NO]; - } else { - [self.activityBarButtonItem setEnabled:![self isDirectorySelected]]; - [self.deleteBarButtonItem setEnabled:YES]; - } - - allItemsSelected = NO; - - return; - } + return numberOfRows; } -- (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { - [self.tableView setEditing:editing animated:animated]; - - [self updateNavigationBarTitle]; - - if (editing) { - self.navigationItem.rightBarButtonItem = self.editBarButtonItem; - self.editBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); - - self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; - [self.toolbar setAlpha:0.0]; - [self.tabBarController.tabBar addSubview:self.toolbar]; - [UIView animateWithDuration:0.33f animations:^ { - [self.toolbar setAlpha:1.0]; - }]; - - for (OfflineTableViewCell *cell in [self.tableView visibleCells]) { - UIView *view = [[UIView alloc] init]; - view.backgroundColor = UIColor.clearColor; - cell.selectedBackgroundView = view; - } - } else { - self.editBarButtonItem.title = AMLocalizedString(@"edit", @"Caption of a button to edit the files that are selected"); - self.navigationItem.rightBarButtonItem = self.moreBarButtonItem; - - allItemsSelected = NO; - self.selectedItems = nil; - self.navigationItem.leftBarButtonItems = @[]; - - [UIView animateWithDuration:0.33f animations:^ { - [self.toolbar setAlpha:0.0]; - } completion:^(BOOL finished) { - if (finished) { - [self.toolbar removeFromSuperview]; - } - }]; - - for (OfflineTableViewCell *cell in [self.tableView visibleCells]) { - cell.selectedBackgroundView = nil; - } - } - - if (!self.selectedItems) { - self.selectedItems = [[NSMutableArray alloc] init]; - - [self.activityBarButtonItem setEnabled:NO]; - [self.deleteBarButtonItem setEnabled:NO]; - } -} - - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - return YES; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - -- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { - UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"Share" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { - OfflineTableViewCell *cell = (OfflineTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]; - NSString *itemPath = [[self currentOfflinePath] stringByAppendingPathComponent:[cell itemNameString]]; - [self removeOfflineNodeCell:itemPath]; - }]; - deleteAction.image = [UIImage imageNamed:@"delete"]; - deleteAction.backgroundColor = [UIColor colorWithRed:0.94 green:0.22 blue:0.23 alpha:1]; - return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]]; -} - -#pragma clang diagnostic pop - #pragma mark - IBActions - (IBAction)editTapped:(UIBarButtonItem *)sender { - BOOL enableEditing = !self.tableView.isEditing; - [self setTableViewEditing:enableEditing animated:YES]; + BOOL enableEditing = self.offlineTableView ? !self.offlineTableView.tableView.isEditing : !self.offlineCollectionView.collectionView.allowsMultipleSelection; + [self setEditMode:enableEditing]; } - (IBAction)selectAllAction:(UIBarButtonItem *)sender { [self.selectedItems removeAllObjects]; - if (!allItemsSelected) { + if (!self.allItemsSelected) { NSURL *filePathURL = nil; for (NSInteger i = 0; i < self.offlineSortedItems.count; i++) { @@ -833,9 +608,9 @@ - (IBAction)selectAllAction:(UIBarButtonItem *)sender { [self.selectedItems addObject:filePathURL]; } - allItemsSelected = YES; + self.allItemsSelected = YES; } else { - allItemsSelected = NO; + self.allItemsSelected = NO; } if (self.selectedItems.count == 0) { @@ -848,7 +623,7 @@ - (IBAction)selectAllAction:(UIBarButtonItem *)sender { [self updateNavigationBarTitle]; - [self.tableView reloadData]; + [self reloadData]; } - (IBAction)activityTapped:(UIBarButtonItem *)sender { @@ -864,7 +639,7 @@ - (IBAction)activityTapped:(UIBarButtonItem *)sender { activityViewController.popoverPresentationController.barButtonItem = self.activityBarButtonItem; [activityViewController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { if (completed) { - [self setTableViewEditing:NO animated:YES]; + [self setEditMode:NO]; } }]; @@ -885,7 +660,7 @@ - (IBAction)deleteTapped:(UIBarButtonItem *)sender { [self removeOfflineNodeCell:url.path]; } [self reloadUI]; - [self setTableViewEditing:NO animated:YES]; + [self setEditMode:NO]; }]]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -905,6 +680,15 @@ - (IBAction)moreAction:(UIBarButtonItem *)sender { UIAlertController *moreAlertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; [moreAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + if ([self numberOfRows]) { + NSString *changeViewTitle = (self.layoutView == LayoutModeList) ? AMLocalizedString(@"Thumbnail view", @"Text shown for switching from list view to thumbnail view.") : AMLocalizedString(@"List view", @"Text shown for switching from thumbnail view to list view."); + UIAlertAction *changeViewAlertAction = [UIAlertAction actionWithTitle:changeViewTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [self changeLayoutMode]; + }]; + [changeViewAlertAction setValue:[UIColor mnz_black333333] forKey:@"titleTextColor"]; + [moreAlertController addAction:changeViewAlertAction]; + } + UIAlertAction *sortByAlertAction = [UIAlertAction actionWithTitle:AMLocalizedString(@"sortTitle", @"Section title of the 'Sort by'") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self sortByTapped:self.sortByBarButtonItem]; }]; @@ -928,12 +712,211 @@ - (IBAction)moreAction:(UIBarButtonItem *)sender { [self presentViewController:moreAlertController animated:YES completion:nil]; } +#pragma mark - Public + +- (void)enableButtonsByNumberOfItems { + NSInteger rows = self.searchController.isActive ? self.searchItemsArray.count : self.offlineSortedItems.count; + if (rows == 0) { + self.sortByBarButtonItem.enabled = NO; + [self.editBarButtonItem setEnabled:NO]; + } else { + self.sortByBarButtonItem.enabled = YES; + [self.editBarButtonItem setEnabled:YES]; + } +} + +- (void)enableButtonsBySelectedItems { + if (self.selectedItems.count == 0) { + [self.activityBarButtonItem setEnabled:NO]; + [self.deleteBarButtonItem setEnabled:NO]; + } else { + [self.activityBarButtonItem setEnabled:![self isDirectorySelected]]; + [self.deleteBarButtonItem setEnabled:YES]; + } +} + +- (void)itemTapped:(NSString *)name atIndexPath:(NSIndexPath *)indexPath { + self.previewDocumentPath = [[self currentOfflinePath] stringByAppendingPathComponent:name]; + + BOOL isDirectory; + [[NSFileManager defaultManager] fileExistsAtPath:self.previewDocumentPath isDirectory:&isDirectory]; + if (isDirectory) { + NSString *folderPathFromOffline = [self folderPathFromOffline:self.previewDocumentPath folder:name]; + + OfflineViewController *offlineVC = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineViewControllerID"]; + [offlineVC setFolderPathFromOffline:folderPathFromOffline]; + if (self.searchController.isActive) { + [self.searchController dismissViewControllerAnimated:YES completion:^{ + [self.navigationController pushViewController:offlineVC animated:YES]; + }]; + } else { + [self.navigationController pushViewController:offlineVC animated:YES]; + } + } else if (self.previewDocumentPath.mnz_isMultimediaPathExtension) { + AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:self.previewDocumentPath]]; + + if (asset.playable) { + MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithURL:[NSURL fileURLWithPath:self.previewDocumentPath]]; + [self presentViewController:megaAVViewController animated:YES completion:nil]; + } else { + MEGAQLPreviewController *previewController = [self qlPreviewControllerForIndexPath:indexPath]; + [self presentViewController:previewController animated:YES completion:nil]; + } + + } else if ([self.previewDocumentPath.pathExtension isEqualToString:@"pdf"]){ + MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"previewDocumentNavigationID"]; + PreviewDocumentViewController *previewController = navigationController.viewControllers.firstObject; + previewController.filesPathsArray = self.offlineFiles; + previewController.nodeFileIndex = [[[self itemAtIndexPath:indexPath] objectForKey:kIndex] integerValue]; + [self presentViewController:navigationController animated:YES completion:nil]; + switch (self.layoutView) { + case LayoutModeList: + [self.offlineTableView.tableView deselectRowAtIndexPath:indexPath animated:YES]; + break; + + case LayoutModeThumbnail: + [self.offlineCollectionView.collectionView deselectItemAtIndexPath:indexPath animated:YES]; + break; + } + } else { + MEGAQLPreviewController *previewController = [self qlPreviewControllerForIndexPath:indexPath]; + [self presentViewController:previewController animated:YES completion:nil]; + } +} + +- (void)setViewEditing:(BOOL)editing { + [self updateNavigationBarTitle]; + + if (editing) { + self.navigationItem.rightBarButtonItem = self.editBarButtonItem; + self.editBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); + self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; + [self.toolbar setAlpha:0.0]; + [self.tabBarController.tabBar addSubview:self.toolbar]; + [UIView animateWithDuration:0.33f animations:^ { + [self.toolbar setAlpha:1.0]; + }]; + } else { + self.editBarButtonItem.title = AMLocalizedString(@"edit", @"Caption of a button to edit the files that are selected"); + self.navigationItem.rightBarButtonItem = self.moreBarButtonItem; + self.allItemsSelected = NO; + self.selectedItems = nil; + self.navigationItem.leftBarButtonItems = @[]; + + [UIView animateWithDuration:0.33f animations:^ { + [self.toolbar setAlpha:0.0]; + } completion:^(BOOL finished) { + if (finished) { + [self.toolbar removeFromSuperview]; + } + }]; + } + + if (!self.selectedItems) { + self.selectedItems = [[NSMutableArray alloc] init]; + + [self.activityBarButtonItem setEnabled:NO]; + [self.deleteBarButtonItem setEnabled:NO]; + } +} + +- (BOOL)removeOfflineNodeCell:(NSString *)itemPath { + NSArray *offlinePathsOnFolderArray; + MOOfflineNode *offlineNode; + + BOOL isDirectory; + [[NSFileManager defaultManager] fileExistsAtPath:itemPath isDirectory:&isDirectory]; + if (isDirectory) { + if ([[[[MEGASdkManager sharedMEGASdk] transfers] size] integerValue]) { + [self cancelPendingTransfersOnFolder:itemPath folderLink:NO]; + } + if ([[[[MEGASdkManager sharedMEGASdkFolder] transfers] size] integerValue]) { + [self cancelPendingTransfersOnFolder:itemPath folderLink:YES]; + } + offlinePathsOnFolderArray = [self offlinePathOnFolder:itemPath]; + } + + NSError *error = nil; + BOOL success = [[NSFileManager defaultManager] removeItemAtPath:itemPath error:&error]; + offlineNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:[Helper pathRelativeToOfflineDirectory:itemPath]]; + if (!success || error) { + [SVProgressHUD showErrorWithStatus:@""]; + return NO; + } else { + if (isDirectory) { + for (NSString *localPathAux in offlinePathsOnFolderArray) { + offlineNode = [[MEGAStore shareInstance] fetchOfflineNodeWithPath:localPathAux]; + if (offlineNode) { + [[MEGAStore shareInstance] removeOfflineNode:offlineNode]; + } + } + } else { + if (offlineNode) { + [[MEGAStore shareInstance] removeOfflineNode:offlineNode]; + } + } + [self reloadUI]; + return YES; + } +} + +- (void)updateNavigationBarTitle { + NSString *navigationTitle; + if (self.offlineTableView.tableView.isEditing || self.offlineCollectionView.collectionView.allowsMultipleSelection) { + if (self.selectedItems.count == 0) { + navigationTitle = AMLocalizedString(@"selectTitle", @"Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos"); + } else { + navigationTitle = (self.selectedItems.count == 1) ? [NSString stringWithFormat:AMLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedItems.count] : [NSString stringWithFormat:AMLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedItems.count]; + } + } else { + if (self.folderPathFromOffline == nil) { + navigationTitle = AMLocalizedString(@"offline", @"Offline"); + } else { + navigationTitle = self.folderPathFromOffline.lastPathComponent; + } + } + + self.navigationItem.title = navigationTitle; +} + +- (NSDictionary *)itemAtIndexPath:(NSIndexPath *)indexPath { + NSDictionary *item = nil; + if (indexPath) { + if (self.searchController.isActive) { + item = [self.searchItemsArray objectAtIndex:indexPath.row]; + } else { + item = [self.offlineSortedItems objectAtIndex:indexPath.row]; + } + } + return item; +} + +- (NSString *)currentOfflinePath { + NSString *pathString = [Helper pathForOffline]; + if (self.folderPathFromOffline != nil) { + pathString = [pathString stringByAppendingPathComponent:self.folderPathFromOffline]; + } + return pathString; +} + #pragma mark - UISearchBarDelegate - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { self.searchItemsArray = nil; } +- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { + if (self.layoutView == LayoutModeThumbnail) { + self.offlineCollectionView.collectionView.clipsToBounds = YES; + } +} + +- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { + if (self.layoutView == LayoutModeThumbnail) { + self.offlineCollectionView.collectionView.clipsToBounds = NO; + } +} + #pragma mark - UISearchResultsUpdating - (void)updateSearchResultsForSearchController:(UISearchController *)searchController { @@ -947,89 +930,109 @@ - (void)updateSearchResultsForSearchController:(UISearchController *)searchContr } } - [self.tableView reloadData]; + [self reloadData]; } #pragma mark - UILongPressGestureRecognizer - (void)longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer { - CGPoint touchPoint = [longPressGestureRecognizer locationInView:self.tableView]; - NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:touchPoint]; - - if (!indexPath || ![self.tableView numberOfRowsInSection:indexPath.section]) { - return; + UIView *view = self.offlineTableView ? self.offlineTableView.tableView : self.offlineCollectionView.collectionView; + CGPoint touchPoint = [longPressGestureRecognizer locationInView:view]; + NSIndexPath *indexPath; + + if (self.layoutView == LayoutModeList) { + indexPath = [self.offlineTableView.tableView indexPathForRowAtPoint:touchPoint]; + if (!indexPath || ![self.offlineTableView.tableView numberOfRowsInSection:indexPath.section]) { + return; + } + } else { + indexPath = [self.offlineCollectionView.collectionView indexPathForItemAtPoint:touchPoint]; + if (!indexPath || ![self.offlineCollectionView.collectionView numberOfItemsInSection:indexPath.section]) { + return; + } } if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) { - if (self.tableView.isEditing) { + if (self.offlineTableView.tableView.isEditing) { // Only stop editing if long pressed over a cell that is the only one selected or when selected none if (self.selectedItems.count == 0) { - [self setTableViewEditing:NO animated:YES]; + [self setEditMode:NO]; } if (self.selectedItems.count == 1) { NSURL *offlineUrlSelected = self.selectedItems.firstObject; NSURL *offlineUrlPressed = [[self.offlineSortedItems objectAtIndex:indexPath.row] objectForKey:kPath]; if ([[offlineUrlPressed path] compare:[offlineUrlSelected path]] == NSOrderedSame) { - [self setTableViewEditing:NO animated:YES]; + [self setEditMode:NO]; } } } else { - [self setTableViewEditing:YES animated:YES]; - [self tableView:self.tableView didSelectRowAtIndexPath:indexPath]; - [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; + [self setEditMode:YES]; + [self selectIndexPath:indexPath]; } } if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) { - [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + [self.offlineTableView.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; } } #pragma mark - UIViewControllerPreviewingDelegate - (UIViewController *)previewingContext:(id)previewingContext viewControllerForLocation:(CGPoint)location { - if (self.tableView.isEditing) { + if (self.offlineTableView.tableView.isEditing || self.offlineCollectionView.collectionView.allowsMultipleSelection) { return nil; } - CGPoint rowPoint = [self.tableView convertPoint:location fromView:self.view]; - NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rowPoint]; - if (!indexPath || ![self.tableView numberOfRowsInSection:indexPath.section]) { - return nil; + NSIndexPath *indexPath; + NSString *itemName; + if (self.layoutView == LayoutModeList) { + CGPoint rowPoint = [self.offlineTableView.tableView convertPoint:location fromView:self.view]; + indexPath = [self.offlineTableView.tableView indexPathForRowAtPoint:rowPoint]; + if (!indexPath || ![self.offlineTableView.tableView numberOfRowsInSection:indexPath.section]) { + return nil; + } + OfflineTableViewCell *cell = (OfflineTableViewCell *)[self.offlineTableView.tableView cellForRowAtIndexPath:indexPath]; + itemName = cell.itemNameString; + } else { + CGPoint rowPoint = [self.offlineCollectionView.collectionView convertPoint:location fromView:self.view]; + indexPath = [self.offlineCollectionView.collectionView indexPathForItemAtPoint:rowPoint]; + if (!indexPath || ![self.offlineCollectionView.collectionView numberOfItemsInSection:indexPath.section]) { + return nil; + } + NodeCollectionViewCell *cell = (NodeCollectionViewCell *)[self.offlineCollectionView.collectionView cellForItemAtIndexPath:indexPath]; + itemName = cell.nameLabel.text; } - previewingContext.sourceRect = [self.tableView convertRect:[self.tableView cellForRowAtIndexPath:indexPath].frame toView:self.view]; + previewingContext.sourceRect = (self.layoutView == LayoutModeList) ? [self.offlineTableView.tableView convertRect:[self.offlineTableView.tableView cellForRowAtIndexPath:indexPath].frame toView:self.view] : [self.offlineCollectionView.collectionView convertRect:[self.offlineCollectionView.collectionView cellForItemAtIndexPath:indexPath].frame toView:self.view]; - OfflineTableViewCell *cell = (OfflineTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; - NSString *itemNameString = cell.itemNameString; - previewDocumentPath = [[self currentOfflinePath] stringByAppendingPathComponent:itemNameString]; + self.previewDocumentPath = [[self currentOfflinePath] stringByAppendingPathComponent:itemName]; BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:previewDocumentPath isDirectory:&isDirectory]; + [[NSFileManager defaultManager] fileExistsAtPath:self.previewDocumentPath isDirectory:&isDirectory]; if (isDirectory) { - NSString *folderPathFromOffline = [self folderPathFromOffline:previewDocumentPath folder:cell.itemNameString]; + NSString *folderPathFromOffline = [self folderPathFromOffline:self.previewDocumentPath folder:itemName]; OfflineViewController *offlineVC = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineViewControllerID"]; offlineVC.folderPathFromOffline = folderPathFromOffline; offlineVC.peekIndexPath = indexPath; return offlineVC; - } else if (previewDocumentPath.mnz_isMultimediaPathExtension) { - AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:previewDocumentPath]]; + } else if (self.previewDocumentPath.mnz_isMultimediaPathExtension) { + AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:self.previewDocumentPath]]; if (asset.playable) { - MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithURL:[NSURL fileURLWithPath:previewDocumentPath]]; + MEGAAVViewController *megaAVViewController = [[MEGAAVViewController alloc] initWithURL:[NSURL fileURLWithPath:self.previewDocumentPath]]; return megaAVViewController; } else { return [self qlPreviewControllerForIndexPath:indexPath]; } - } else if ([previewDocumentPath.pathExtension isEqualToString:@"pdf"]){ + } else if ([self.previewDocumentPath.pathExtension isEqualToString:@"pdf"]){ MEGANavigationController *navigationController = [[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateViewControllerWithIdentifier:@"previewDocumentNavigationID"]; PreviewDocumentViewController *previewController = navigationController.viewControllers.firstObject; previewController.filesPathsArray = self.offlineFiles; previewController.nodeFileIndex = [[[self itemAtIndexPath:indexPath] objectForKey:kIndex] integerValue]; - [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; + [self.offlineTableView.tableView deselectRowAtIndexPath:indexPath animated:YES]; return navigationController; } else { @@ -1051,10 +1054,10 @@ - (void)previewingContext:(id)previewingContext comm UIPreviewAction *deleteAction = [UIPreviewAction actionWithTitle:AMLocalizedString(@"remove", @"Title for the action that allows to remove a file or folder") style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) { - OfflineViewController *offlineVC = (OfflineViewController *)previewViewController; - [offlineVC tableView:offlineVC.tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:offlineVC.peekIndexPath]; + if ([self removeOfflineNodeCell:[self currentOfflinePath]]) { + [self reloadData]; + } }]; - return @[deleteAction]; } @@ -1127,44 +1130,4 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: } } -#pragma mark - MGSwipeTableCellDelegate - -- (BOOL)swipeTableCell:(MGSwipeTableCell*) cell canSwipe:(MGSwipeDirection) direction fromPoint:(CGPoint)point { - if (self.tableView.isEditing) { - return NO; - } - - if (direction == MGSwipeDirectionLeftToRight) { - return NO; - } - - return YES; -} - -- (NSArray*)swipeTableCell:(MGSwipeTableCell*) cell swipeButtonsForDirection:(MGSwipeDirection)direction - swipeSettings:(MGSwipeSettings*) swipeSettings expansionSettings:(MGSwipeExpansionSettings*) expansionSettings { - - swipeSettings.transition = MGSwipeTransitionDrag; - expansionSettings.buttonIndex = 0; - expansionSettings.expansionLayout = MGSwipeExpansionLayoutCenter; - expansionSettings.fillOnTrigger = NO; - expansionSettings.threshold = 2; - - if (direction == MGSwipeDirectionRightToLeft) { - - MGSwipeButton *deleteButton = [MGSwipeButton buttonWithTitle:@"" icon:[UIImage imageNamed:@"delete"] backgroundColor:[UIColor colorWithRed:0.93 green:0.22 blue:0.23 alpha:1.0] padding:25 callback:^BOOL(MGSwipeTableCell *sender) { - OfflineTableViewCell *offlineCell = (OfflineTableViewCell *)cell; - NSString *itemPath = [[self currentOfflinePath] stringByAppendingPathComponent:[offlineCell itemNameString]]; - [self removeOfflineNodeCell:itemPath]; - return YES; - }]; - [deleteButton iconTintColor:[UIColor whiteColor]]; - - return @[deleteButton]; - } - else { - return nil; - } -} - @end diff --git a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.h b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.h index feb93f6061..e5149b2cc5 100644 --- a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.h +++ b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.h @@ -11,8 +11,9 @@ typedef NS_ENUM(NSUInteger, MEGAPhotoMode) { MEGAPhotoModeOriginal }; -@protocol MEGAPhotoBrowserDelegate +@protocol MEGAPhotoBrowserDelegate +@optional - (void)photoBrowser:(MEGAPhotoBrowserViewController *)photoBrowser didPresentNode:(MEGANode *)node; - (void)photoBrowser:(MEGAPhotoBrowserViewController *)photoBrowser willDismissWithNode:(MEGANode *)node; @@ -30,5 +31,6 @@ typedef NS_ENUM(NSUInteger, MEGAPhotoMode) { @property (nonatomic, weak) id delegate; @property (nonatomic) DisplayMode displayMode; @property (nonatomic) NSString *publicLink; +@property (nonatomic) NSString *encryptedLink; @end diff --git a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.m b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.m index aa93379ef8..2f8500add8 100644 --- a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.m +++ b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.m @@ -112,12 +112,21 @@ - (void)viewDidLoad { self.pieChartView.layer.cornerRadius = self.pieChartView.frame.size.width/2; self.pieChartView.layer.masksToBounds = YES; - if (self.displayMode == DisplayModeFileLink) { - self.leftToolbarItem.image = nil; - self.leftToolbarItem.title = AMLocalizedString(@"download", nil); - - self.rightToolbarItem.image = nil; - self.rightToolbarItem.title = AMLocalizedString(@"import", @"Button title that triggers the importing link action"); + switch (self.displayMode) { + case DisplayModeFileLink: + self.leftToolbarItem.image = nil; + self.leftToolbarItem.title = AMLocalizedString(@"download", nil); + + self.rightToolbarItem.image = nil; + self.rightToolbarItem.title = AMLocalizedString(@"import", @"Button title that triggers the importing link action"); + break; + + case DisplayModeSharedItem: + [self.toolbar setItems:@[self.leftToolbarItem]]; + break; + + default: + break; } } @@ -182,7 +191,7 @@ - (void)reloadUI { self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * self.mediaNodes.count, self.scrollView.frame.size.height); if (self.currentIndex >= self.mediaNodes.count) { - MEGALogError(@"MEGAPhotoBrowserViewController tried to show the node at index %lu, with %lu items in the array of nodes", self.currentIndex, self.mediaNodes.count); + MEGALogError(@"MEGAPhotoBrowserViewController tried to show the node at index %tu, with %tu items in the array of nodes", self.currentIndex, self.mediaNodes.count); if (self.mediaNodes.count > 0) { self.currentIndex = self.mediaNodes.count - 1; } else { @@ -195,7 +204,9 @@ - (void)reloadUI { self.scrollView.contentOffset = CGPointMake(self.currentIndex * (self.view.frame.size.width + self.gapBetweenPages), 0); [self reloadTitle]; [self airplayDisplayCurrentImage]; - [self.delegate photoBrowser:self didPresentNode:[self.mediaNodes objectAtIndex:self.currentIndex]]; + if ([self.delegate respondsToSelector:@selector(photoBrowser:didPresentNode:)]) { + [self.delegate photoBrowser:self didPresentNode:[self.mediaNodes objectAtIndex:self.currentIndex]]; + } } - (void)reloadTitle { @@ -265,7 +276,9 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self resetZooms]; [self reloadTitle]; [self airplayDisplayCurrentImage]; - [self.delegate photoBrowser:self didPresentNode:[self.mediaNodes objectAtIndex:self.currentIndex]]; + if ([self.delegate respondsToSelector:@selector(photoBrowser:didPresentNode:)]) { + [self.delegate photoBrowser:self didPresentNode:[self.mediaNodes objectAtIndex:self.currentIndex]]; + } } } } @@ -551,7 +564,9 @@ - (IBAction)didPressCloseButton:(UIBarButtonItem *)sender { [self toggleTransparentInterfaceForDismissal:YES]; [self dismissViewControllerAnimated:YES completion:^{ - [self.delegate photoBrowser:self willDismissWithNode:node]; + if ([self.delegate respondsToSelector:@selector(photoBrowser:willDismissWithNode:)]) { + [self.delegate photoBrowser:self willDismissWithNode:node]; + } }]; } @@ -611,7 +626,7 @@ - (IBAction)didPressRightToolbarButton:(UIBarButtonItem *)sender { MEGAActivityItemProvider *activityItemProvider = [[MEGAActivityItemProvider alloc] initWithPlaceholderString:node.name node:node]; NSMutableArray *activitiesMutableArray = [[NSMutableArray alloc] init]; if (node.name.mnz_isImagePathExtension) { - SaveToCameraRollActivity *saveToCameraRollActivity = [[SaveToCameraRollActivity alloc] initWithNode:node]; + SaveToCameraRollActivity *saveToCameraRollActivity = [[SaveToCameraRollActivity alloc] initWithNode:node api:self.api]; [activitiesMutableArray addObject:saveToCameraRollActivity]; } activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[activityItemProvider] applicationActivities:activitiesMutableArray]; @@ -664,7 +679,9 @@ - (void)panGesture:(UIPanGestureRecognizer *)panGestureRecognizer { case UIGestureRecognizerStateCancelled: { if (ABS(verticalIncrement) > 50.0f) { [self dismissViewControllerAnimated:YES completion:^{ - [self.delegate photoBrowser:self willDismissWithNode:node]; + if ([self.delegate respondsToSelector:@selector(photoBrowser:willDismissWithNode:)]) { + [self.delegate photoBrowser:self willDismissWithNode:node]; + } }]; } else { [UIView animateWithDuration:0.3 animations:^{ @@ -857,7 +874,8 @@ - (void)performAction:(MegaNodeActionType)action inNode:(MEGANode *)node fromSen switch (self.displayMode) { case DisplayModeFileLink: { - activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.publicLink] applicationActivities:nil]; + NSString *link = self.encryptedLink ? self.encryptedLink : self.publicLink; + activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[link] applicationActivities:nil]; [activityVC setExcludedActivityTypes:@[UIActivityTypePrint, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, UIActivityTypeAddToReadingList, UIActivityTypeAirDrop]]; [activityVC.popoverPresentationController setBarButtonItem:sender]; @@ -949,6 +967,10 @@ - (void)performAction:(MegaNodeActionType)action inNode:(MEGANode *)node fromSen [node mnz_removeInViewController:self]; break; + case MegaNodeActionTypeSaveToPhotos: + [node mnz_saveToPhotosWithApi:self.api]; + break; + default: break; } @@ -962,12 +984,15 @@ - (void)presentParentNode:(MEGANode *)node { [self toggleTransparentInterfaceForDismissal:YES]; [self dismissViewControllerAnimated:YES completion:^{ - [self.delegate photoBrowser:self willDismissWithNode:node]; - UIViewController *visibleViewController = UIApplication.mnz_visibleViewController; + if ([self.delegate respondsToSelector:@selector(photoBrowser:willDismissWithNode:)]) { + [self.delegate photoBrowser:self willDismissWithNode:node]; + } + UIViewController *visibleViewController = UIApplication.mnz_presentingViewController; if ([visibleViewController isKindOfClass:MainTabBarController.class]) { NSArray *parentTreeArray = node.mnz_parentTreeArray; - UINavigationController *navigationController = (UINavigationController *)((MainTabBarController *)visibleViewController).viewControllers[((MainTabBarController *)visibleViewController).selectedIndex]; + MainTabBarController *mainTBC = (MainTabBarController *)visibleViewController; + UINavigationController *navigationController = (UINavigationController *)(mainTBC.selectedViewController); [navigationController popToRootViewControllerAnimated:NO]; for (MEGANode *node in parentTreeArray) { diff --git a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.storyboard b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.storyboard index bae368ecdc..b72b0eab4f 100644 --- a/iMEGA/Photo browser/MEGAPhotoBrowserViewController.storyboard +++ b/iMEGA/Photo browser/MEGAPhotoBrowserViewController.storyboard @@ -35,7 +35,7 @@ - + diff --git a/iMEGA/Settings/About/AboutTableViewController.m b/iMEGA/Settings/About/AboutTableViewController.m index 6df1bad455..35def34d20 100644 --- a/iMEGA/Settings/About/AboutTableViewController.m +++ b/iMEGA/Settings/About/AboutTableViewController.m @@ -6,6 +6,7 @@ #import "Helper.h" #import "MEGAReachabilityManager.h" #import "MEGASdkManager.h" +#import "NSURL+MNZCategory.h" @interface AboutTableViewController () @@ -38,10 +39,10 @@ - (void)viewDidLoad { self.versionCell.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; self.sdkVersionLabel.text = AMLocalizedString(@"sdkVersion", @"Title of the label where the SDK version is shown"); - self.sdkVersionSHALabel.text = @"d902e1a5"; + self.sdkVersionSHALabel.text = @"4911017d"; self.megachatSdkVersionLabel.text = AMLocalizedString(@"megachatSdkVersion", @"Title of the label where the MEGAchat SDK version is shown"); - self.megachatSdkSHALabel.text = @"b81e8314"; + self.megachatSdkSHALabel.text = @"b5fadce9"; self.viewSourceCodeLabel.text = AMLocalizedString(@"View source code", @"Link to the public code of the ap"); @@ -83,9 +84,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case 1: { if (indexPath.row == 0) { //View source code - [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:@"https://github.com/meganz/iOS"]]; + [[NSURL URLWithString:@"https://github.com/meganz/iOS"] mnz_presentSafariViewController]; } else if (indexPath.row == 1) { //Acknowledgements - [Helper presentSafariViewControllerWithURL:[NSURL URLWithString:@"https://github.com/meganz/iOS3/blob/master/CREDITS.md"]]; + [[NSURL URLWithString:@"https://github.com/meganz/iOS3/blob/master/CREDITS.md"] mnz_presentSafariViewController]; } break; } diff --git a/iMEGA/Settings/Advanced/AdvancedTableViewController.m b/iMEGA/Settings/Advanced/AdvancedTableViewController.m index f452a93e26..95092982a7 100644 --- a/iMEGA/Settings/Advanced/AdvancedTableViewController.m +++ b/iMEGA/Settings/Advanced/AdvancedTableViewController.m @@ -59,9 +59,9 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.dontUseHttpLabel setText:AMLocalizedString(@"dontUseHttp", @"Text next to a switch that allows disabling the HTTP protocol for transfers")]; - self.savePhotosLabel.text = AMLocalizedString(@"Save Images in Library", @"Settings section title where you can enable the option to 'Save Images in Library'"); - self.saveVideosLabel.text = AMLocalizedString(@"Save Videos in Library", @"Settings section title where you can enable the option to 'Save Videos in Library'"); - self.saveMediaInGalleryLabel.text = AMLocalizedString(@"Save in Library", @"Settings section title where you can enable the option to 'Save in Library' the images or videos taken from your camera in the MEGA app"); + self.savePhotosLabel.text = AMLocalizedString(@"Save Images in Photos", @"Settings section title where you can enable the option to 'Save Images in Photos'"); + self.saveVideosLabel.text = AMLocalizedString(@"Save Videos in Photos", @"Settings section title where you can enable the option to 'Save Videos in Photos'"); + self.saveMediaInGalleryLabel.text = AMLocalizedString(@"Save in Photos", @"Settings section title where you can enable the option to 'Save in Photos' the images or videos taken from your camera in the MEGA app"); BOOL useHttpsOnly = [[[NSUserDefaults alloc] initWithSuiteName:@"group.mega.ios"] boolForKey:@"useHttpsOnly"]; [self.useHttpsOnlySwitch setOn:useHttpsOnly]; @@ -175,7 +175,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte } case 2: { //Camera - titleFooter = AMLocalizedString(@"Save a copy of the images and videos taken from the MEGA app in your device’s media library.", @"Footer text shown under the Camera setting to explain the option 'Save in Library'"); + titleFooter = AMLocalizedString(@"Save a copy of the images and videos taken from the MEGA app in your device’s media library.", @"Footer text shown under the Camera setting to explain the option 'Save in Photos'"); break; } } diff --git a/iMEGA/Settings/Chat/ChatSettings.storyboard b/iMEGA/Settings/Chat/ChatSettings.storyboard index 5347432eb6..d880f511ab 100644 --- a/iMEGA/Settings/Chat/ChatSettings.storyboard +++ b/iMEGA/Settings/Chat/ChatSettings.storyboard @@ -1,11 +1,11 @@ - + - + @@ -35,7 +35,7 @@ - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - + @@ -1025,28 +995,21 @@ + - - - - + + - - - - + - - - - + + - @@ -2231,7 +2194,7 @@ You may still receive file versions from shared folders if your contacts have th - + - - - - + + + + + + + + + + + + + + + + - - - - + + + + @@ -159,8 +111,6 @@ - - diff --git a/iMEGA/Vendor/CTAssetsPickerController b/iMEGA/Vendor/CTAssetsPickerController index 256e381db2..116fc6c398 160000 --- a/iMEGA/Vendor/CTAssetsPickerController +++ b/iMEGA/Vendor/CTAssetsPickerController @@ -1 +1 @@ -Subproject commit 256e381db2e619c9736fa8d134b97490b660de6e +Subproject commit 116fc6c3989c78b63a09db9cf5241d4744b85fc3 diff --git a/iMEGA/Vendor/JSQMessagesViewController b/iMEGA/Vendor/JSQMessagesViewController index 8f9eb67bef..7b2eeb9370 160000 --- a/iMEGA/Vendor/JSQMessagesViewController +++ b/iMEGA/Vendor/JSQMessagesViewController @@ -1 +1 @@ -Subproject commit 8f9eb67befe2c4451d6a16de4439c47bb85d334a +Subproject commit 7b2eeb93701c3b4a91445a237d2fd639b726a83b diff --git a/iMEGA/Vendor/Karere b/iMEGA/Vendor/Karere index eabbb954d6..9513c31474 160000 --- a/iMEGA/Vendor/Karere +++ b/iMEGA/Vendor/Karere @@ -1 +1 @@ -Subproject commit eabbb954d6ceaed76789056075985be89bfa2144 +Subproject commit 9513c3147484bcdb67bc986afbdbe1f1fbaa4ec6 diff --git a/iMEGA/Vendor/MGSwipeTableCell b/iMEGA/Vendor/MGSwipeTableCell index 8fdb8146f9..eeb3575aa6 160000 --- a/iMEGA/Vendor/MGSwipeTableCell +++ b/iMEGA/Vendor/MGSwipeTableCell @@ -1 +1 @@ -Subproject commit 8fdb8146f95ff39c74e0155d49be4c8f7d17c2b4 +Subproject commit eeb3575aa698e533dcc5567238188ecf2f35b94c diff --git a/iMEGA/Vendor/PieChartView b/iMEGA/Vendor/PieChartView index 251a2d0ca5..81371d89f1 160000 --- a/iMEGA/Vendor/PieChartView +++ b/iMEGA/Vendor/PieChartView @@ -1 +1 @@ -Subproject commit 251a2d0ca5e28ec52878f74eb172cbfeab954bdc +Subproject commit 81371d89f13bbc54c8e14758150fee83d4f5ac34 diff --git a/iMEGA/Vendor/PureLayout b/iMEGA/Vendor/PureLayout index 8fe63c5f93..8de4fdbff8 160000 --- a/iMEGA/Vendor/PureLayout +++ b/iMEGA/Vendor/PureLayout @@ -1 +1 @@ -Subproject commit 8fe63c5f93245982512a4e8fce7b220f601a51cf +Subproject commit 8de4fdbff8a34830625cf22cc03b317bf1158cf1 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index b04c8530ec..9c5e0d9ce5 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit b04c8530ecbb3c1c9b2e75650baf4d1a7ed29cb4 +Subproject commit 9c5e0d9ce5247add8f12151d90a9d9ff988b63c7 diff --git a/iMEGA/Vendor/SVProgressHUD b/iMEGA/Vendor/SVProgressHUD index d860511141..e2a6aaa123 160000 --- a/iMEGA/Vendor/SVProgressHUD +++ b/iMEGA/Vendor/SVProgressHUD @@ -1 +1 @@ -Subproject commit d860511141f71fa0fb40c3241a0701be06ad7838 +Subproject commit e2a6aaa12321962cbe9fbecc370475f369c6566c From 0244ce6f7414e06d845156e8e5de60c02fef76cd Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Mon, 17 Dec 2018 14:51:56 +0100 Subject: [PATCH 14/58] Use DevicePermissionsHelper's methods to ask for permissions when Saving to Photos.app --- iMEGA/Categories/MEGANode+MNZCategory.m | 49 ++++++++----------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/iMEGA/Categories/MEGANode+MNZCategory.m b/iMEGA/Categories/MEGANode+MNZCategory.m index a37a5bb04e..1336e9b633 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Categories/MEGANode+MNZCategory.m @@ -8,6 +8,7 @@ #import "MEGA-Swift.h" +#import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGAMoveRequestDelegate.h" #import "MEGANodeList+MNZCategory.h" @@ -218,40 +219,22 @@ - (BOOL)mnz_downloadNodeOverwriting:(BOOL)overwrite api:(MEGASdk *)api { } - (void)mnz_saveToPhotosWithApi:(MEGASdk *)api { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusAuthorized: { - [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saving to Photos…", @"Text shown when starting the process to save a photo or video to Photos app")]; - NSString *temporaryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:self.base64Handle] stringByAppendingPathComponent:self.name]; - NSString *temporaryFingerprint = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:temporaryPath]; - if ([temporaryFingerprint isEqualToString:self.fingerprint]) { - [self mnz_copyToGalleryFromTemporaryPath:temporaryPath]; - } else if ([MEGAReachabilityManager isReachableHUDIfNot]) { - NSString *downloadsDirectory = [[NSFileManager defaultManager] downloadsDirectory]; - downloadsDirectory = downloadsDirectory.mnz_relativeLocalPath; - NSString *offlineNameString = [[MEGASdkManager sharedMEGASdkFolder] escapeFsIncompatible:self.name]; - NSString *localPath = [downloadsDirectory stringByAppendingPathComponent:offlineNameString]; - [[MEGASdkManager sharedMEGASdk] startDownloadNode:[api authorizeNode:self] localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp]]; - } - break; + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + if (granted) { + [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saving to Photos…", @"Text shown when starting the process to save a photo or video to Photos app")]; + NSString *temporaryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:self.base64Handle] stringByAppendingPathComponent:self.name]; + NSString *temporaryFingerprint = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:temporaryPath]; + if ([temporaryFingerprint isEqualToString:self.fingerprint]) { + [self mnz_copyToGalleryFromTemporaryPath:temporaryPath]; + } else if ([MEGAReachabilityManager isReachableHUDIfNot]) { + NSString *downloadsDirectory = [NSFileManager.defaultManager downloadsDirectory]; + downloadsDirectory = downloadsDirectory.mnz_relativeLocalPath; + NSString *offlineNameString = [[MEGASdkManager sharedMEGASdkFolder] escapeFsIncompatible:self.name]; + NSString *localPath = [downloadsDirectory stringByAppendingPathComponent:offlineNameString]; + [[MEGASdkManager sharedMEGASdk] startDownloadNode:[api authorizeNode:self] localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp]]; } - - case PHAuthorizationStatusRestricted: - case PHAuthorizationStatusDenied: { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - - [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; - }]]; - - [UIApplication.mnz_visibleViewController presentViewController:permissionsAlertController animated:YES completion:nil]; - break; - } - - default: - break; + } else { + [DevicePermissionsHelper alertPhotosPermission]; } }]; } From 21efdb9444e1e2fe07c9e2bf2ac3454bc73c3de8 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Tue, 18 Dec 2018 13:52:49 +0100 Subject: [PATCH 15/58] Code styling - Refactored on-boarding/permissions code, now in Obj-C with storyboard/xib. Reverted project configuration. - Updated submodules. --- MEGA.xcodeproj/project.pbxproj | 72 +++-- iMEGA/AppDelegate.m | 7 +- iMEGA/Categories/MEGANode+MNZCategory.m | 7 +- iMEGA/Categories/String+MNZExtension.swift | 12 - iMEGA/Launch/InitialLaunchViewController.m | 5 +- iMEGA/Links/FolderLinkViewController.m | 7 +- iMEGA/Login/OnboardingInfoView.swift | 114 ------- iMEGA/Login/OnboardingViewController.swift | 282 ------------------ iMEGA/Utils/Headers/OnboardingType.h | 5 + iMEGA/Utils/Headers/OnboardingViewType.h | 10 + .../Onboarding.storyboard | 135 +++++++++ .../OnboardingView/OnboardingView.h | 20 ++ .../OnboardingView/OnboardingView.m | 88 ++++++ .../OnboardingView/OnboardingView.xib | 70 +++++ .../OnboardingViewController.h | 16 + .../OnboardingViewController.m | 206 +++++++++++++ iMEGA/Vendor/Karere | 2 +- iMEGA/Vendor/SDK | 2 +- 18 files changed, 608 insertions(+), 452 deletions(-) delete mode 100644 iMEGA/Categories/String+MNZExtension.swift delete mode 100644 iMEGA/Login/OnboardingInfoView.swift delete mode 100644 iMEGA/Login/OnboardingViewController.swift create mode 100644 iMEGA/Utils/Headers/OnboardingType.h create mode 100644 iMEGA/Utils/Headers/OnboardingViewType.h create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h create mode 100644 iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index f3249e5ff8..90a498a017 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -182,10 +182,10 @@ 5B17C9B31F73DE310093F162 /* InviteFriendsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B17C9B21F73DE310093F162 /* InviteFriendsViewController.m */; }; 5B19713D1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B19713C1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m */; }; 5B1AB4AF20E5407300FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B1AB4AE20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m */; }; - 5B20B00A20E61DDF00511C0F /* MEGALinkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */; }; - 5B20B01120E6300300511C0F /* MEGAGenericRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */; }; 5B1F74D42126E30C00F09A16 /* AwaitingEmailConfirmationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B1F74D22126E30B00F09A16 /* AwaitingEmailConfirmationView.m */; }; 5B1F74D52126E30C00F09A16 /* AwaitingEmailConfirmationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B1F74D32126E30B00F09A16 /* AwaitingEmailConfirmationView.xib */; }; + 5B20B00A20E61DDF00511C0F /* MEGALinkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */; }; + 5B20B01120E6300300511C0F /* MEGAGenericRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */; }; 5B20F4A21EFA9629007E0A34 /* ChatAttachedNodesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B20F4A11EFA9629007E0A34 /* ChatAttachedNodesViewController.m */; }; 5B222A74205A9CD40083D433 /* MEGAChatAttachNodeRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B222A73205A9CD40083D433 /* MEGAChatAttachNodeRequestDelegate.m */; }; 5B22F97C1F8374C500D2B156 /* VENToken.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B22F9771F8374C400D2B156 /* VENToken.xib */; }; @@ -255,7 +255,6 @@ 5BFA346F1FAC5850005BFC4E /* MEGAMoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */; }; 5BFA34701FAC5857005BFC4E /* MEGAMoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */; }; 5BFA34711FAC5BFB005BFC4E /* MEGARemoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA34691FAB4168005BFC4E /* MEGARemoveRequestDelegate.m */; }; - 770629A22153855600613D6E /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770629A12153855600613D6E /* OnboardingViewController.swift */; }; 7714901520F7592100769BD4 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7714901420F7592000769BD4 /* Contacts.framework */; }; 7720C5B5206A931E00F995D1 /* MEGATransfer+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 7720C5B4206A931E00F995D1 /* MEGATransfer+MNZCategory.m */; }; 7720C5B9206AB2E900F995D1 /* SF-UI-Display-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 5B6429E32020A9A2000E0DCB /* SF-UI-Display-Bold.otf */; }; @@ -303,8 +302,6 @@ 7759700C219EBCE700B31671 /* Notifications.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7759700B219EBCE700B31671 /* Notifications.storyboard */; }; 7759700F219EC2D700B31671 /* NotificationTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7759700E219EC2D700B31671 /* NotificationTableViewCell.m */; }; 775D9C0B20EA0D49001BF1E8 /* ShareAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */; }; - 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */; }; - 7776472D2158D45900EC7F4C /* String+MNZExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */; }; 777615AF2175D87D00A7796D /* InputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 777615AE2175D87D00A7796D /* InputView.m */; }; 777615B12175D88C00A7796D /* InputView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 777615B02175D88C00A7796D /* InputView.xib */; }; 777E9229203EB0E200266229 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 777E9228203EB0E100266229 /* CoreImage.framework */; }; @@ -316,6 +313,10 @@ 77892EA020E5029E0020A533 /* MOUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 417B5AAD1DEC792400A193D5 /* MOUser.m */; }; 77892EA120E503580020A533 /* MEGACD.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4147C2CF1B4EE8A400A37044 /* MEGACD.xcdatamodeld */; }; 77892EA220E508300020A533 /* UIImage+GKContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B2D41B1A6D06780002E29E /* UIImage+GKContact.m */; }; + 7795677721C80CDF002B6227 /* OnboardingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7795677621C80CDF002B6227 /* OnboardingViewController.m */; }; + 7795677921C80D36002B6227 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7795677821C80D36002B6227 /* Onboarding.storyboard */; }; + 7795677D21C81C6E002B6227 /* OnboardingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7795677C21C81C6E002B6227 /* OnboardingView.m */; }; + 7795677F21C83130002B6227 /* OnboardingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7795677E21C83130002B6227 /* OnboardingView.xib */; }; 77B32274209C6DE80037FA89 /* MEGARichPreviewMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B32273209C6DE80037FA89 /* MEGARichPreviewMediaItem.m */; }; 77B32277209C6EC00037FA89 /* MEGAMessageRichPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B32276209C6EC00037FA89 /* MEGAMessageRichPreviewView.m */; }; 77B32279209C6F060037FA89 /* MEGAMessageRichPreviewView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77B32278209C6F060037FA89 /* MEGAMessageRichPreviewView.xib */; }; @@ -1148,13 +1149,13 @@ 5B19713C1F2F1D1C00DE1BB2 /* RemoveSharingActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoveSharingActivity.m; sourceTree = ""; }; 5B1AB4AD20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAQuerySignupLinkRequestDelegate.h; path = API/Requests/MEGAQuerySignupLinkRequestDelegate.h; sourceTree = ""; }; 5B1AB4AE20E5407200FEDAF9 /* MEGAQuerySignupLinkRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAQuerySignupLinkRequestDelegate.m; path = API/Requests/MEGAQuerySignupLinkRequestDelegate.m; sourceTree = ""; }; + 5B1F74D12126E30B00F09A16 /* AwaitingEmailConfirmationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AwaitingEmailConfirmationView.h; sourceTree = ""; }; + 5B1F74D22126E30B00F09A16 /* AwaitingEmailConfirmationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AwaitingEmailConfirmationView.m; sourceTree = ""; }; + 5B1F74D32126E30B00F09A16 /* AwaitingEmailConfirmationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AwaitingEmailConfirmationView.xib; sourceTree = ""; }; 5B20B00920E61DDF00511C0F /* MEGALinkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGALinkManager.m; sourceTree = ""; }; 5B20B00B20E61DFB00511C0F /* MEGALinkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGALinkManager.h; sourceTree = ""; }; 5B20B00F20E6300300511C0F /* MEGAGenericRequestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MEGAGenericRequestDelegate.h; path = API/Requests/MEGAGenericRequestDelegate.h; sourceTree = ""; }; 5B20B01020E6300300511C0F /* MEGAGenericRequestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MEGAGenericRequestDelegate.m; path = API/Requests/MEGAGenericRequestDelegate.m; sourceTree = ""; }; - 5B1F74D12126E30B00F09A16 /* AwaitingEmailConfirmationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AwaitingEmailConfirmationView.h; sourceTree = ""; }; - 5B1F74D22126E30B00F09A16 /* AwaitingEmailConfirmationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AwaitingEmailConfirmationView.m; sourceTree = ""; }; - 5B1F74D32126E30B00F09A16 /* AwaitingEmailConfirmationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AwaitingEmailConfirmationView.xib; sourceTree = ""; }; 5B20F4A01EFA9629007E0A34 /* ChatAttachedNodesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChatAttachedNodesViewController.h; path = Chat/ChatAttachedNodesViewController.h; sourceTree = ""; }; 5B20F4A11EFA9629007E0A34 /* ChatAttachedNodesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ChatAttachedNodesViewController.m; path = Chat/ChatAttachedNodesViewController.m; sourceTree = ""; }; 5B222A72205A9CD30083D433 /* MEGAChatAttachNodeRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAChatAttachNodeRequestDelegate.h; sourceTree = ""; }; @@ -1247,9 +1248,9 @@ 5BFA346A1FAB4169005BFC4E /* MEGARemoveRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGARemoveRequestDelegate.h; path = API/Requests/MEGARemoveRequestDelegate.h; sourceTree = ""; }; 5BFA346C1FAB6282005BFC4E /* MEGAMoveRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAMoveRequestDelegate.h; path = API/Requests/MEGAMoveRequestDelegate.h; sourceTree = ""; }; 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAMoveRequestDelegate.m; path = API/Requests/MEGAMoveRequestDelegate.m; sourceTree = ""; }; - 770629A02153855600613D6E /* MEGA-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGA-Bridging-Header.h"; sourceTree = ""; }; - 770629A12153855600613D6E /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 7714901420F7592000769BD4 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; + 771A1B5E21C9220C00A8555D /* OnboardingViewType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OnboardingViewType.h; sourceTree = ""; }; + 771A1B5F21C9227A00A8555D /* OnboardingType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OnboardingType.h; sourceTree = ""; }; 7720C5B3206A931E00F995D1 /* MEGATransfer+MNZCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGATransfer+MNZCategory.h"; sourceTree = ""; }; 7720C5B4206A931E00F995D1 /* MEGATransfer+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MEGATransfer+MNZCategory.m"; sourceTree = ""; }; 773DF39A20EE1470004C3418 /* SendMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendMode.h; sourceTree = ""; }; @@ -1280,14 +1281,18 @@ 7759700E219EC2D700B31671 /* NotificationTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationTableViewCell.m; sourceTree = ""; }; 775D9C0920EA0D49001BF1E8 /* ShareAttachment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareAttachment.h; sourceTree = ""; }; 775D9C0A20EA0D49001BF1E8 /* ShareAttachment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareAttachment.m; sourceTree = ""; }; - 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingInfoView.swift; sourceTree = ""; }; - 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+MNZExtension.swift"; sourceTree = ""; }; 777615AD2175D87D00A7796D /* InputView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InputView.h; sourceTree = ""; }; 777615AE2175D87D00A7796D /* InputView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InputView.m; sourceTree = ""; }; 777615B02175D88C00A7796D /* InputView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputView.xib; sourceTree = ""; }; 777E9228203EB0E100266229 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; 777E922B203EE14B00266229 /* QRSettingsTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QRSettingsTableViewController.h; sourceTree = ""; }; 777E922C203EE14B00266229 /* QRSettingsTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QRSettingsTableViewController.m; sourceTree = ""; }; + 7795677521C80CDF002B6227 /* OnboardingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OnboardingViewController.h; sourceTree = ""; }; + 7795677621C80CDF002B6227 /* OnboardingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OnboardingViewController.m; sourceTree = ""; }; + 7795677821C80D36002B6227 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; + 7795677B21C81C6E002B6227 /* OnboardingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OnboardingView.h; sourceTree = ""; }; + 7795677C21C81C6E002B6227 /* OnboardingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OnboardingView.m; sourceTree = ""; }; + 7795677E21C83130002B6227 /* OnboardingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingView.xib; sourceTree = ""; }; 77B32272209C6DE80037FA89 /* MEGARichPreviewMediaItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGARichPreviewMediaItem.h; sourceTree = ""; }; 77B32273209C6DE80037FA89 /* MEGARichPreviewMediaItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGARichPreviewMediaItem.m; sourceTree = ""; }; 77B32275209C6EC00037FA89 /* MEGAMessageRichPreviewView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAMessageRichPreviewView.h; sourceTree = ""; }; @@ -1914,7 +1919,6 @@ 1373618F1A6664C300B740E8 /* main.m */, 415227411A692F8D00EC7BB6 /* Localizable.strings */, 4122C0E71B27539F001CE833 /* MEGA-PrefixHeader.pch */, - 770629A02153855600613D6E /* MEGA-Bridging-Header.h */, ); name = "Supporting Files"; sourceTree = ""; @@ -2089,8 +2093,6 @@ E8D4B88C1A69429200A3B2E2 /* ConfirmAccountViewController.m */, A8F5D6781ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.h */, A8F5D6791ED58F940087DA40 /* CheckEmailAndFollowTheLinkViewController.m */, - 770629A12153855600613D6E /* OnboardingViewController.swift */, - 7776472A2154E08100EC7F4C /* OnboardingInfoView.swift */, ); path = Login; sourceTree = ""; @@ -2568,7 +2570,6 @@ 77F43DD82034637300AE4F42 /* UICollectionView+MNZCategory.m */, 77B3227D20A436D60037FA89 /* NSURL+MNZCategory.h */, 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */, - 7776472C2158D45900EC7F4C /* String+MNZExtension.swift */, 5B0CD4282188D71100F97A2C /* UITextField+MNZCategory.h */, 5B0CD4292188D71100F97A2C /* UITextField+MNZCategory.m */, 77597004219DD4D100B31671 /* MEGAUserAlertList+MNZCategory.h */, @@ -2876,6 +2877,8 @@ 5B72A14B20D3A69C007FE4FD /* URLType.h */, 836A987C20F8C8DA00EC719B /* ChatRoomsType.h */, 83064D88217F207000FA3DAC /* LayoutView.h */, + 771A1B5E21C9220C00A8555D /* OnboardingViewType.h */, + 771A1B5F21C9227A00A8555D /* OnboardingType.h */, ); path = Headers; sourceTree = ""; @@ -3004,6 +3007,27 @@ path = Model; sourceTree = ""; }; + 7795677421C80C43002B6227 /* OnboardingViewController */ = { + isa = PBXGroup; + children = ( + 7795677A21C81C5C002B6227 /* OnboardingView */, + 7795677821C80D36002B6227 /* Onboarding.storyboard */, + 7795677521C80CDF002B6227 /* OnboardingViewController.h */, + 7795677621C80CDF002B6227 /* OnboardingViewController.m */, + ); + path = OnboardingViewController; + sourceTree = ""; + }; + 7795677A21C81C5C002B6227 /* OnboardingView */ = { + isa = PBXGroup; + children = ( + 7795677B21C81C6E002B6227 /* OnboardingView.h */, + 7795677C21C81C6E002B6227 /* OnboardingView.m */, + 7795677E21C83130002B6227 /* OnboardingView.xib */, + ); + path = OnboardingView; + sourceTree = ""; + }; 77D9A5692023538600B48470 /* Photo browser */ = { isa = PBXGroup; children = ( @@ -3254,6 +3278,7 @@ children = ( 832E1020202B097900BDD30F /* CustomActionViewController */, 83D64E2B2017873D00E24155 /* PasswordReminder */, + 7795677421C80C43002B6227 /* OnboardingViewController */, A819B33D1EAFA7E2004592F9 /* CustomModalAlertViewController.h */, A819B33E1EAFA7E2004592F9 /* CustomModalAlertViewController.m */, A819B3411EAFA844004592F9 /* CustomModalAlertViewController.xib */, @@ -3607,7 +3632,6 @@ 137361891A6664C300B740E8 = { CreatedOnToolsVersion = 6.1.1; DevelopmentTeam = T9RH74Y7L9; - LastSwiftMigration = 1000; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -3750,6 +3774,7 @@ 41EABF171DA7C5250087271C /* Chat.storyboard in Resources */, 5B6429E42020A9A3000E0DCB /* SF-UI-Display-Bold.otf in Resources */, E8C289F51BF5F8A200F7A034 /* SF-UI-Text-Light.otf in Resources */, + 7795677F21C83130002B6227 /* OnboardingView.xib in Resources */, 5BB68CA220359B5400B03C00 /* PasswordStrengthIndicatorView.xib in Resources */, 41A72F1F1C22EA1600516603 /* LTHPasscodeViewController.bundle in Resources */, E8CD198A1A9270CB00CB8B2A /* UnavailableLinkView.xib in Resources */, @@ -3758,6 +3783,7 @@ A841E63E20CAB477005E1D1B /* MEGAMessageCallEndedView.xib in Resources */, 417279331DB663FA001AC818 /* JSQMessagesAssets.bundle in Resources */, 9462A1811FDFFD4800008F32 /* MEGAToolbarTextContentView.xib in Resources */, + 7795677921C80D36002B6227 /* Onboarding.storyboard in Resources */, A89E77491EA605A900ADC0D2 /* MEGAMessageAttachmentView.xib in Resources */, 4152273A1A692EEF00EC7BB6 /* SVProgressHUD.bundle in Resources */, 94B120BA1FE2B6CB0099E382 /* MEGAToolbarImagePickerView.xib in Resources */, @@ -3910,7 +3936,6 @@ E8BD127F1DB76E03003DC53F /* MasterKeyViewController.m in Sources */, 7759700A219EB85300B31671 /* NotificationsTableViewController.m in Sources */, 41D21E691D01AEC3002E6296 /* CTAssetsNavigationController.m in Sources */, - 7776472B2154E08100EC7F4C /* OnboardingInfoView.swift in Sources */, 41D578241B84A0FB00334ED3 /* PreviewDocumentViewController.m in Sources */, 7759700F219EC2D700B31671 /* NotificationTableViewCell.m in Sources */, 5BFA346B1FAB4169005BFC4E /* MEGARemoveRequestDelegate.m in Sources */, @@ -4060,7 +4085,6 @@ 41D21DF21D01AE89002E6296 /* NSLayoutConstraint+PureLayout.m in Sources */, A8F0A1D82178A73300575C57 /* MOMessage+CoreDataClass.m in Sources */, A810CF3020C157BC00A6C6C5 /* FLAnimatedImageView.m in Sources */, - 770629A22153855600613D6E /* OnboardingViewController.swift in Sources */, E8F791801BC5754A00C58676 /* OpenInActivity.m in Sources */, 774FA6642090F5A20061DD85 /* MEGADialogMediaItem.m in Sources */, 417279571DB663FA001AC818 /* JSQMessagesLabel.m in Sources */, @@ -4122,7 +4146,6 @@ 836AEDE52018AD1F00868FC7 /* PasswordView.m in Sources */, 836E74C42099DC82005E2317 /* MEGAChatChangeGroupNameRequestDelegate.m in Sources */, 41D21E621D01AEC3002E6296 /* CTAssetSelectionLabel.m in Sources */, - 7776472D2158D45900EC7F4C /* String+MNZExtension.swift in Sources */, E83E102A1E1582E8002F3E2E /* ChatStatusTableViewController.m in Sources */, 942F4FD81F500A8F001FC4AC /* MEGASdk+MNZCategory.m in Sources */, E85EB30D1DF58EBA0066AC68 /* VerifyCredentialsViewController.m in Sources */, @@ -4199,6 +4222,7 @@ 831D61A5218738D20038F5B8 /* MOFolderLayout+CoreDataClass.m in Sources */, 94624CFB1F50484200D52504 /* MEGAExportRequestDelegate.m in Sources */, 77F1DBDF2056AA13005D5F0E /* MEGAContactLinkQueryRequestDelegate.m in Sources */, + 7795677D21C81C6E002B6227 /* OnboardingView.m in Sources */, 417279451DB663FA001AC818 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */, 5B5A983C212EF29500FDBC79 /* RubbishBinTableViewController.m in Sources */, 5B22F97D1F8374C500D2B156 /* VENToken.m in Sources */, @@ -4230,6 +4254,7 @@ A820DA3C1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m in Sources */, 41136FBC1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m in Sources */, 94F376E71FE7C126009AAA10 /* MEGAToolbarAssetPicker.m in Sources */, + 7795677721C80CDF002B6227 /* OnboardingViewController.m in Sources */, 41FC83C01AAE056C008FA551 /* CameraUploadsTableViewController.m in Sources */, 944B205D1EE55533003C967B /* UIAlertAction+MNZCategory.m in Sources */, 415DCF241BF48EFA00914A1E /* ContactRequestsViewController.m in Sources */, @@ -4629,7 +4654,6 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MEGA.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -4670,9 +4694,6 @@ PRODUCT_NAME = MEGA; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/MEGA-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "iMEGA/Vendor/SDK/bindings/ios/** iMEGA/Vendor/SDK/include/** iMEGA/Vendor/Karere/bindings/Objective-C"; VERSIONING_SYSTEM = ""; @@ -4683,7 +4704,6 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MEGA.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -4721,8 +4741,6 @@ PRODUCT_BUNDLE_IDENTIFIER = mega.ios; PRODUCT_NAME = MEGA; PROVISIONING_PROFILE = ""; - SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/MEGA-Bridging-Header.h"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "iMEGA/Vendor/SDK/bindings/ios/** iMEGA/Vendor/SDK/include/** iMEGA/Vendor/Karere/bindings/Objective-C"; VERSIONING_SYSTEM = ""; diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index e07b571f54..1ac438993c 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -11,8 +11,6 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" -#import "MEGA-Swift.h" - #import "CameraUploads.h" #import "Helper.h" #import "DevicePermissionsHelper.h" @@ -44,6 +42,7 @@ #import "LaunchViewController.h" #import "MainTabBarController.h" #import "MEGAAssetsPickerController.h" +#import "OnboardingViewController.h" #import "UpgradeTableViewController.h" #import "MEGAChatCreateChatGroupRequestDelegate.h" @@ -330,7 +329,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( createAccountRequestDelegate.resumeCreateAccount = YES; [[MEGASdkManager sharedMEGASdk] resumeCreateAccountWithSessionId:sessionId delegate:createAccountRequestDelegate]; } else { - self.window.rootViewController = [OnboardingViewController new]; + self.window.rootViewController = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; } } @@ -905,7 +904,7 @@ - (void)showMainTabBar { } - (void)showOnboarding { - OnboardingViewController *onboardingVC = [OnboardingViewController new]; + OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; dispatch_async(dispatch_get_main_queue(), ^{ UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; [onboardingVC.view addSubview:overlayView]; diff --git a/iMEGA/Categories/MEGANode+MNZCategory.m b/iMEGA/Categories/MEGANode+MNZCategory.m index 1336e9b633..0f55052a0b 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Categories/MEGANode+MNZCategory.m @@ -6,8 +6,6 @@ #import "SAMKeychain.h" #import "SVProgressHUD.h" -#import "MEGA-Swift.h" - #import "DevicePermissionsHelper.h" #import "Helper.h" #import "MEGAMoveRequestDelegate.h" @@ -30,6 +28,7 @@ #import "MEGANavigationController.h" #import "MEGAPhotoBrowserViewController.h" #import "MEGAQLPreviewController.h" +#import "OnboardingViewController.h" #import "PreviewDocumentViewController.h" #import "SharedItemsViewController.h" @@ -419,7 +418,7 @@ - (void)mnz_fileLinkDownloadFromViewController:(UIViewController *)viewControlle MEGALinkManager.selectedOption = LinkOptionDownloadNode; } - OnboardingViewController *onboardingVC = [OnboardingViewController new]; + OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; if (viewController.navigationController) { [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { @@ -451,7 +450,7 @@ - (void)mnz_fileLinkImportFromViewController:(UIViewController *)viewController MEGALinkManager.selectedOption = LinkOptionImportNode; } - OnboardingViewController *onboardingVC = [OnboardingViewController new]; + OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; if (viewController.navigationController) { [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { diff --git a/iMEGA/Categories/String+MNZExtension.swift b/iMEGA/Categories/String+MNZExtension.swift deleted file mode 100644 index 7019e2d703..0000000000 --- a/iMEGA/Categories/String+MNZExtension.swift +++ /dev/null @@ -1,12 +0,0 @@ - -import Foundation - -extension String { - func localized() -> String { - return localized(withComment: nil) - } - - func localized(withComment comment: String!) -> String { - return LocalizationSystem.sharedLocal()?.localizedString(forKey: self, value: comment) ?? self - } -} diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index 390e186f17..f8d6df7426 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -1,7 +1,7 @@ #import "InitialLaunchViewController.h" -#import "MEGA-Swift.h" +#import "OnboardingViewController.h" @interface InitialLaunchViewController () @@ -40,8 +40,7 @@ - (void)performAnimation { #pragma mark - IBActions - (IBAction)setupButtonPressed:(UIButton *)sender { - OnboardingViewController *setupVC = [OnboardingViewController new]; - setupVC.type = OnboardingViewControllerTypePermissions; + OnboardingViewController *setupVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypePermissions]; setupVC.completion = ^{ [self.delegate setupFinished]; }; diff --git a/iMEGA/Links/FolderLinkViewController.m b/iMEGA/Links/FolderLinkViewController.m index 70e31df691..172a8c5bb3 100644 --- a/iMEGA/Links/FolderLinkViewController.m +++ b/iMEGA/Links/FolderLinkViewController.m @@ -4,8 +4,6 @@ #import "SAMKeychain.h" #import "UIScrollView+EmptyDataSet.h" -#import "MEGA-Swift.h" - #import "DisplayMode.h" #import "Helper.h" #import "MEGANavigationController.h" @@ -25,6 +23,7 @@ #import "CustomActionViewController.h" #import "NodeTableViewCell.h" #import "MainTabBarController.h" +#import "OnboardingViewController.h" #import "LoginViewController.h" #import "LinkOption.h" #import "UnavailableLinkView.h" @@ -519,7 +518,7 @@ - (IBAction)downloadAction:(UIBarButtonItem *)sender { } MEGALinkManager.selectedOption = LinkOptionDownloadFolderOrNodes; - [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault] animated:YES]; } } @@ -551,7 +550,7 @@ - (IBAction)importAction:(UIBarButtonItem *)sender { } MEGALinkManager.selectedOption = LinkOptionImportFolderOrNodes; - [self.navigationController pushViewController:[OnboardingViewController new] animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault] animated:YES]; } return; diff --git a/iMEGA/Login/OnboardingInfoView.swift b/iMEGA/Login/OnboardingInfoView.swift deleted file mode 100644 index 401400860e..0000000000 --- a/iMEGA/Login/OnboardingInfoView.swift +++ /dev/null @@ -1,114 +0,0 @@ - -import UIKit - -enum OnboardingInfoViewType { - case encryptionInfo - case chatInfo - case contactsInfo - case cameraUploadsInfo - case photosPermission - case microphoneAndCameraPermissions - case notificationsPermission -} - -class OnboardingInfoView: UIView { - - let type:OnboardingInfoViewType - private let imageView: UIImageView = { - let view = UIImageView.newAutoLayout() - view.contentMode = .scaleAspectFit - view.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - return view - }() - private let titleLabel: UILabel = { - let label = UILabel.newAutoLayout() - label.font = UIFont.mnz_SFUIMedium(withSize: 19) - label.setContentHuggingPriority(.defaultHigh, for: .vertical) - return label - }() - private let descriptionLabel: UILabel = { - let label = UILabel.newAutoLayout() - label.lineBreakMode = .byWordWrapping - label.numberOfLines = 0 - label.font = UIFont.mnz_SFUIRegular(withSize: 14) - label.textAlignment = .center - label.setContentHuggingPriority(.defaultHigh, for: .vertical) - return label - }() - - private var didSetupConstraints = false - - - - // MARK: Lifecycle - - public init(type: OnboardingInfoViewType) { - self.type = type - super.init(frame: CGRect.zero) - - switch type { - case .encryptionInfo: - imageView.image = UIImage(named: "privacy_warning_ico") - titleLabel.text = "You hold the keys".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys") - descriptionLabel.text = "Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files.".localized(withComment: "Description shown in a page of the onboarding screens explaining the encryption paradigm") - case .chatInfo: - imageView.image = UIImage(named: "privacy_warning_ico") - titleLabel.text = "Encrypted chat".localized(withComment: "Title shown in a page of the on boarding screens explaining that the chat is encrypted") - descriptionLabel.text = "Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive.".localized(withComment: "Description shown in a page of the onboarding screens explaining the chat feature") - case .contactsInfo: - imageView.image = UIImage(named: "privacy_warning_ico") - titleLabel.text = "Create your Network".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate") - descriptionLabel.text = "Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA".localized(withComment: "Description shown in a page of the onboarding screens explaining contacts") - case .cameraUploadsInfo: - imageView.image = UIImage(named: "privacy_warning_ico") - titleLabel.text = "Your Photos in the Cloud".localized(withComment: "Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically") - descriptionLabel.text = "Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now.".localized(withComment: "Description shown in a page of the onboarding screens explaining the camera uploads feature") - case .photosPermission: - imageView.image = UIImage(named: "photosPermission") - titleLabel.text = "Allow Access to Photos".localized(withComment: "Title label that explains that the user is going to be asked for the photos permission") - descriptionLabel.text = "Please give the MEGA App permission to access Photos to share photos and videos.".localized(withComment: "Detailed explanation of why the user should give permission to access to the photos") - case .microphoneAndCameraPermissions: - imageView.image = UIImage(named: "groupChat") - titleLabel.text = "Enable Microphone and Camera".localized(withComment: "Title label that explains that the user is going to be asked for the microphone and camera permission") - descriptionLabel.text = "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone".localized(withComment: "Detailed explanation of why the user should give permission to access to the camera and the microphone") - case .notificationsPermission: - imageView.image = UIImage(named: "privacy_warning_ico") - titleLabel.text = "Enable Notifications".localized(withComment: "Title label that explains that the user is going to be asked for the notifications permission") - descriptionLabel.text = "We would like to send you notifications so you receive new messages on your device instantly.".localized(withComment: "Detailed explanation of why the user should give permission to deliver notifications") - } - - self.addSubview(imageView) - self.addSubview(titleLabel) - self.addSubview(descriptionLabel) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func updateConstraints() { - if !didSetupConstraints { - setupConstraints() - didSetupConstraints = true - } - super.updateConstraints() - } - - - - // MARK: Autolayout - - private func setupConstraints() { - imageView.autoPinEdge(toSuperviewEdge: .top) - imageView.autoAlignAxis(toSuperviewAxis: .vertical) - - titleLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: 28) - titleLabel.autoAlignAxis(toSuperviewAxis: .vertical) - - descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 28) - descriptionLabel.autoAlignAxis(toSuperviewAxis: .vertical) - descriptionLabel.autoPinEdge(.left, to: .left, of: self, withOffset: 35) - descriptionLabel.autoPinEdge(toSuperviewEdge: .bottom) - } - -} diff --git a/iMEGA/Login/OnboardingViewController.swift b/iMEGA/Login/OnboardingViewController.swift deleted file mode 100644 index b082773aed..0000000000 --- a/iMEGA/Login/OnboardingViewController.swift +++ /dev/null @@ -1,282 +0,0 @@ - -import UIKit - -@objc enum OnboardingViewControllerType: Int { - case onboarding - case permissions -} - -class OnboardingViewController: UIViewController, UIScrollViewDelegate { - - @objc var type = OnboardingViewControllerType.onboarding - @objc var completion: (() -> Void)? - - private let scrollView: UIScrollView = { - let view = UIScrollView.newAutoLayout() - view.isPagingEnabled = true - view.showsHorizontalScrollIndicator = false - return view - }() - private let pageControl: UIPageControl = { - let control = UIPageControl.newAutoLayout() - control.pageIndicatorTintColor = UIColor.mnz_grayD8D8D8() - return control - }() - private let primaryButton: UIButton = { - let button = UIButton.newAutoLayout() - button.layer.cornerRadius = 8 - button.setTitleColor(UIColor.white, for: .normal) - button.titleLabel?.font = UIFont.mnz_SFUISemiBold(withSize: 17) - return button - }() - private let secondaryButton: UIButton = { - let button = UIButton.newAutoLayout() - button.titleLabel?.font = UIFont.mnz_SFUISemiBold(withSize: 17) - return button - }() - - private let contentView = UIView.newAutoLayout() - - private var didSetupConstraints = false - - override var shouldAutorotate: Bool { - return false - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return .portrait - } - - - - // MARK: Lifecycle - - override func loadView() { - view = UIView() - view.backgroundColor = UIColor.white - - view.addSubview(scrollView) - view.addSubview(pageControl) - view.addSubview(primaryButton) - view.addSubview(secondaryButton) - - scrollView.addSubview(contentView) - - view.setNeedsUpdateConstraints() - } - - override func viewDidLoad() { - switch type { - case .onboarding: - pageControl.currentPageIndicatorTintColor = UIColor.mnz_redMain() - pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged) - - primaryButton.setTitle("createAccount".localized(withComment: "Button title which triggers the action to create a MEGA account"), for: .normal) - primaryButton.backgroundColor = UIColor.mnz_redMain() - - secondaryButton.setTitle("login".localized(withComment: "Button title which triggers the action to login in your MEGA account"), for: .normal) - secondaryButton.setTitleColor(UIColor.mnz_redMain(), for: .normal) - - contentView.addSubview({ - let view = OnboardingInfoView(type: .encryptionInfo) - view.configureForAutoLayout() - return view - }()) - contentView.addSubview({ - let view = OnboardingInfoView(type: .chatInfo) - view.configureForAutoLayout() - return view - }()) - contentView.addSubview({ - let view = OnboardingInfoView(type: .contactsInfo) - view.configureForAutoLayout() - return view - }()) - contentView.addSubview({ - let view = OnboardingInfoView(type: .cameraUploadsInfo) - view.configureForAutoLayout() - return view - }()) - - case .permissions: - scrollView.isUserInteractionEnabled = false; - - pageControl.currentPageIndicatorTintColor = UIColor.mnz_green00BFA5() - pageControl.isUserInteractionEnabled = false; - - primaryButton.setTitle("Allow Access".localized(withComment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), for: .normal) - primaryButton.backgroundColor = UIColor.mnz_green00BFA5() - - secondaryButton.setTitle("notNow".localized(), for: .normal) - secondaryButton.setTitleColor(UIColor.mnz_green899B9C(), for: .normal) - - if DevicePermissionsHelper.shouldAskForPhotosPermissions() { - contentView.addSubview({ - let view = OnboardingInfoView(type: .photosPermission) - view.configureForAutoLayout() - return view - }()) - } - if DevicePermissionsHelper.shouldAskForAudioPermissions() || DevicePermissionsHelper.shouldAskForVideoPermissions() { - contentView.addSubview({ - let view = OnboardingInfoView(type: .microphoneAndCameraPermissions) - view.configureForAutoLayout() - return view - }()) - } - if DevicePermissionsHelper.shouldAskForNotificationsPermissions() { - contentView.addSubview({ - let view = OnboardingInfoView(type: .notificationsPermission) - view.configureForAutoLayout() - return view - }()) - } - } - - scrollView.delegate = self - pageControl.numberOfPages = contentView.subviews.count - primaryButton.addTarget(self, action: #selector(primaryButtonTapped), for: .touchUpInside) - secondaryButton.addTarget(self, action: #selector(secondaryButtonTapped), for: .touchUpInside) - } - - override func updateViewConstraints() { - if !didSetupConstraints { - setupConstraints() - didSetupConstraints = true - } - super.updateViewConstraints() - } - - - - // MARK: Autolayout - - private func setupConstraints() { - let pageControlTopOffset:CGFloat = UIDevice.current.iPhone4X || UIDevice.current.iPhone5X ? -22 : -44 - let pageControlBottomOffset:CGFloat = UIDevice.current.iPhone4X || UIDevice.current.iPhone5X ? -29 : -58 - - scrollView.autoPinEdge(.top, to: .top, of: view, withOffset: 64) - scrollView.autoPinEdge(toSuperviewEdge: .left) - scrollView.autoPinEdge(toSuperviewEdge: .right) - scrollView.autoPinEdge(.bottom, to: .top, of: pageControl, withOffset: pageControlTopOffset) - - pageControl.autoPinEdge(toSuperviewEdge: .left) - pageControl.autoPinEdge(toSuperviewEdge: .right) - pageControl.autoPinEdge(.bottom, to: .top, of: primaryButton, withOffset: pageControlBottomOffset) - pageControl.autoSetDimension(.height, toSize: 44) - - primaryButton.autoPinEdge(.left, to: .left, of: view, withOffset: 44) - primaryButton.autoPinEdge(.right, to: .right, of: view, withOffset: -44) - primaryButton.autoPinEdge(.bottom, to: .top, of: secondaryButton, withOffset: -16) - primaryButton.autoSetDimension(.height, toSize: 50) - - secondaryButton.autoPinEdge(.left, to: .left, of: view, withOffset: 44) - secondaryButton.autoPinEdge(.right, to: .right, of: view, withOffset: -44) - secondaryButton.autoPinEdge(.bottom, to: .bottom, of: view, withOffset: -35) - secondaryButton.autoSetDimension(.height, toSize: 50) - - contentView.autoPinEdgesToSuperviewEdges() - contentView.autoMatch(.height, to: .height, of: scrollView) - - for (index, pageView) in contentView.subviews.enumerated() { - pageView.autoPinEdge(toSuperviewEdge: .top) - pageView.autoPinEdge(toSuperviewEdge: .bottom) - pageView.autoMatch(.width, to: .width, of: scrollView) - - if index == 0 { - pageView.autoPinEdge(toSuperviewEdge: .left) - } - - if index == (contentView.subviews.count - 1) { - pageView.autoPinEdge(toSuperviewEdge: .right) - } - - if contentView.subviews.count > 1 && index < (contentView.subviews.count - 1) { - let nextPageView = contentView.subviews[index + 1] - pageView.autoPinEdge(.right, to: .left, of: nextPageView) - } - } - } - - - - // MARK: Private - - private func scrollTo(page: Int) { - let newX = CGFloat(page) * scrollView.frame.width; - scrollView.contentOffset = CGPoint(x: newX, y: 0) - pageControl.currentPage = page - } - - private func nextPageOrDismiss() { - let nextPage = self.pageControl.currentPage + 1 - if nextPage < self.pageControl.numberOfPages { - self.scrollTo(page: nextPage) - } else { - self.dismiss(animated: true) { - self.completion?() - } - } - } - - - - // MARK: UIScrollViewDelegate - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - let newPage = scrollView.contentOffset.x / scrollView.frame.width - pageControl.currentPage = Int(newPage) - } - - - - // MARK: Targets - - @objc func pageControlValueChanged() { - scrollTo(page: pageControl.currentPage) - } - - @objc func primaryButtonTapped() { - switch type { - case .onboarding: - let createAccountNC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CreateAccountNavigationControllerID") - present(createAccountNC, animated: true, completion: nil) - - case .permissions: - let currentView: OnboardingInfoView = contentView.subviews[pageControl.currentPage] as! OnboardingInfoView - switch currentView.type { - case .photosPermission: - DevicePermissionsHelper.photosPermission { (_) in - self.nextPageOrDismiss() - } - - case .microphoneAndCameraPermissions: - DevicePermissionsHelper.audioPermissionModal(false, forIncomingCall: false) { (_) in - DevicePermissionsHelper.videoPermission { (_) in - self.nextPageOrDismiss() - } - } - - case .notificationsPermission: - DevicePermissionsHelper.notificationsPermission { (_) in - self.nextPageOrDismiss() - } - - default: - nextPageOrDismiss() - } - } - } - - @objc func secondaryButtonTapped() { - switch type { - case .onboarding: - let createAccountNC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginNavigationControllerID") - present(createAccountNC, animated: true, completion: nil) - - case .permissions: - nextPageOrDismiss() - } - } - -} diff --git a/iMEGA/Utils/Headers/OnboardingType.h b/iMEGA/Utils/Headers/OnboardingType.h new file mode 100644 index 0000000000..5d114d4986 --- /dev/null +++ b/iMEGA/Utils/Headers/OnboardingType.h @@ -0,0 +1,5 @@ + +typedef NS_ENUM(NSUInteger, OnboardingType) { + OnboardingTypeDefault, + OnboardingTypePermissions +}; diff --git a/iMEGA/Utils/Headers/OnboardingViewType.h b/iMEGA/Utils/Headers/OnboardingViewType.h new file mode 100644 index 0000000000..33f78badd4 --- /dev/null +++ b/iMEGA/Utils/Headers/OnboardingViewType.h @@ -0,0 +1,10 @@ + +typedef NS_ENUM(NSUInteger, OnboardingViewType) { + OnboardingViewTypeEncryptionInfo, + OnboardingViewTypeChatInfo, + OnboardingViewTypeContactsInfo, + OnboardingViewTypeCameraUploadsInfo, + OnboardingViewTypePhotosPermission, + OnboardingViewTypeMicrophoneAndCameraPermissions, + OnboardingViewTypeNotificationsPermission +}; diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard b/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard new file mode 100644 index 0000000000..d905216442 --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h new file mode 100644 index 0000000000..a6f03502e3 --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h @@ -0,0 +1,20 @@ + +#import + +#import "OnboardingViewType.h" + +NS_ASSUME_NONNULL_BEGIN + +IB_DESIGNABLE +@interface OnboardingView : UIView + +@property (nonatomic) UIView *customView; +@property (nonatomic) OnboardingViewType type; + +@property (weak, nonatomic) IBOutlet UIImageView *imageView; +@property (weak, nonatomic) IBOutlet UILabel *titleLabel; +@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m new file mode 100644 index 0000000000..67547b2e6d --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m @@ -0,0 +1,88 @@ + +#import "OnboardingView.h" + +@implementation OnboardingView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self customInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self customInit]; + } + + return self; +} + +- (void)prepareForInterfaceBuilder { + [super prepareForInterfaceBuilder]; + [self customInit]; + [self.customView prepareForInterfaceBuilder]; +} + +#pragma mark - Private + +- (void)customInit { + self.customView = [[NSBundle bundleForClass:self.class] loadNibNamed:@"OnboardingView" owner:self options:nil].firstObject; + [self addSubview:self.customView]; + self.customView.frame = self.bounds; + self.customView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +} + +#pragma mark - Setters + +- (void)setType:(OnboardingViewType)type { + _type = type; + switch (self.type) { + case OnboardingViewTypeEncryptionInfo: + self.imageView.image = [UIImage imageNamed:@"privacy_warning_ico"]; // TODO: Change the image + self.titleLabel.text = AMLocalizedString(@"You hold the keys", @"Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys"); + self.descriptionLabel.text = AMLocalizedString(@"Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files.", @"Description shown in a page of the onboarding screens explaining the encryption paradigm"); + break; + + case OnboardingViewTypeChatInfo: + self.imageView.image = [UIImage imageNamed:@"privacy_warning_ico"]; // TODO: Change the image + self.titleLabel.text = AMLocalizedString(@"Encrypted chat", @"Title shown in a page of the on boarding screens explaining that the chat is encrypted"); + self.descriptionLabel.text = AMLocalizedString(@"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive.", @"Description shown in a page of the onboarding screens explaining the chat feature"); + break; + + case OnboardingViewTypeContactsInfo: + self.imageView.image = [UIImage imageNamed:@"privacy_warning_ico"]; // TODO: Change the image + self.titleLabel.text = AMLocalizedString(@"Create your Network", @"Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate"); + self.descriptionLabel.text = AMLocalizedString(@"Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA", @"Description shown in a page of the onboarding screens explaining contacts"); + break; + + case OnboardingViewTypeCameraUploadsInfo: + self.imageView.image = [UIImage imageNamed:@"privacy_warning_ico"]; // TODO: Change the image + self.titleLabel.text = AMLocalizedString(@"Your Photos in the Cloud", @"Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically"); + self.descriptionLabel.text = AMLocalizedString(@"Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now.", @"Description shown in a page of the onboarding screens explaining the camera uploads feature"); + break; + + case OnboardingViewTypePhotosPermission: + self.imageView.image = [UIImage imageNamed:@"photosPermission"]; + self.titleLabel.text = AMLocalizedString(@"Allow Access to Photos", @"Title label that explains that the user is going to be asked for the photos permission"); + self.descriptionLabel.text = AMLocalizedString(@"Please give the MEGA App permission to access Photos to share photos and videos.", @"Detailed explanation of why the user should give permission to access to the photos"); + break; + + case OnboardingViewTypeMicrophoneAndCameraPermissions: + self.imageView.image = [UIImage imageNamed:@"groupChat"]; + self.titleLabel.text = AMLocalizedString(@"Enable Microphone and Camera", @"Title label that explains that the user is going to be asked for the microphone and camera permission"); + self.descriptionLabel.text = AMLocalizedString(@"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", @"Detailed explanation of why the user should give permission to access to the camera and the microphone"); + break; + + case OnboardingViewTypeNotificationsPermission: + self.imageView.image = [UIImage imageNamed:@"privacy_warning_ico"]; + self.titleLabel.text = AMLocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); + self.descriptionLabel.text = AMLocalizedString(@"We would like to send you notifications so you receive new messages on your device instantly.", @"Detailed explanation of why the user should give permission to deliver notifications"); + break; + } +} + +@end diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib new file mode 100644 index 0000000000..e7c99b2d88 --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h new file mode 100644 index 0000000000..9cff7e74e5 --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h @@ -0,0 +1,16 @@ + +#import + +#import "OnboardingType.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OnboardingViewController : UIViewController + ++ (OnboardingViewController *)onboardingViewControllerOfType:(OnboardingType)type; + +@property (nonatomic, copy) void (^completion)(void); + +@end + +NS_ASSUME_NONNULL_END diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m new file mode 100644 index 0000000000..28e2d5a8e2 --- /dev/null +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m @@ -0,0 +1,206 @@ + +#import "OnboardingViewController.h" + +#import "DevicePermissionsHelper.h" +#import "OnboardingView.h" +#import "UIColor+MNZCategory.h" + +@interface OnboardingViewController () + +@property (nonatomic) OnboardingType type; + +@property (weak, nonatomic) IBOutlet UIScrollView *scrollView; +@property (weak, nonatomic) IBOutlet UIPageControl *pageControl; +@property (weak, nonatomic) IBOutlet UIButton *primaryButton; +@property (weak, nonatomic) IBOutlet UIButton *secondaryButton; + +@end + +@implementation OnboardingViewController + +#pragma mark - Initialization + ++ (OnboardingViewController *)onboardingViewControllerOfType:(OnboardingType)type { + OnboardingViewController *onboardingViewController = [[UIStoryboard storyboardWithName:@"Onboarding" bundle:nil] instantiateViewControllerWithIdentifier:@"OnboardingViewControllerID"]; + onboardingViewController.type = type; + return onboardingViewController; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + switch (self.type) { + case OnboardingTypeDefault: + self.pageControl.currentPageIndicatorTintColor = UIColor.mnz_redMain; + [self.pageControl addTarget:self action:@selector(pageControlValueChanged) forControlEvents:UIControlEventValueChanged]; + + [self.primaryButton setTitle:AMLocalizedString(@"createAccount", @"Button title which triggers the action to create a MEGA account") forState:UIControlStateNormal]; + self.primaryButton.backgroundColor = UIColor.mnz_redMain; + + [self.secondaryButton setTitle:AMLocalizedString(@"login", @"Button title which triggers the action to login in your MEGA account") forState:UIControlStateNormal]; + [self.secondaryButton setTitleColor:UIColor.mnz_redMain forState:UIControlStateNormal]; + + if (self.scrollView.subviews.firstObject.subviews.count == 4) { + OnboardingView *onboardingViewEncryption = self.scrollView.subviews.firstObject.subviews[0]; + onboardingViewEncryption.type = OnboardingViewTypeEncryptionInfo; + OnboardingView *onboardingViewChat = self.scrollView.subviews.firstObject.subviews[1]; + onboardingViewChat.type = OnboardingViewTypeChatInfo; + OnboardingView *onboardingViewContacts = self.scrollView.subviews.firstObject.subviews[2]; + onboardingViewContacts.type = OnboardingViewTypeContactsInfo; + OnboardingView *onboardingViewCameraUploads = self.scrollView.subviews.firstObject.subviews[3]; + onboardingViewCameraUploads.type = OnboardingViewTypeCameraUploadsInfo; + } + + break; + + case OnboardingTypePermissions: + self.scrollView.userInteractionEnabled = NO; + + self.pageControl.currentPageIndicatorTintColor = UIColor.mnz_green00BFA5; + self.pageControl.userInteractionEnabled = NO; + + [self.primaryButton setTitle:AMLocalizedString(@"Allow Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand") forState:UIControlStateNormal]; + self.primaryButton.backgroundColor = UIColor.mnz_green00BFA5; + + [self.secondaryButton setTitle:AMLocalizedString(@"notNow", nil) forState:UIControlStateNormal]; + [self.secondaryButton setTitleColor:UIColor.mnz_green899B9C forState:UIControlStateNormal]; + + if (self.scrollView.subviews.firstObject.subviews.count == 4) { + [self.scrollView.subviews.firstObject.subviews.lastObject removeFromSuperview]; + int nextIndex = 0; + if ([DevicePermissionsHelper shouldAskForPhotosPermissions]) { + OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; + onboardingView.type = OnboardingViewTypePhotosPermission; + nextIndex++; + } else { + [self.scrollView.subviews.firstObject.subviews[nextIndex] removeFromSuperview]; + } + + if ([DevicePermissionsHelper shouldAskForAudioPermissions] || [DevicePermissionsHelper shouldAskForVideoPermissions]) { + OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; + onboardingView.type = OnboardingViewTypeMicrophoneAndCameraPermissions; + nextIndex++; + } else { + [self.scrollView.subviews.firstObject.subviews[nextIndex] removeFromSuperview]; + } + + if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; + onboardingView.type = OnboardingViewTypeNotificationsPermission; + nextIndex++; + } else { + [self.scrollView.subviews.firstObject.subviews[nextIndex] removeFromSuperview]; + } + } + + break; + } + + self.scrollView.delegate = self; + self.pageControl.numberOfPages = self.scrollView.subviews.firstObject.subviews.count; +} + +#pragma mark - Rotation settings + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if (UIDevice.currentDevice.iPhoneDevice) { + return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; + } + + return UIInterfaceOrientationMaskAll; +} + +#pragma mark - Private + +- (void)scrollTo:(NSUInteger)page { + CGFloat newX = (CGFloat)page * self.scrollView.frame.size.width; + self.scrollView.contentOffset = CGPointMake(newX, 0.0f); + self.pageControl.currentPage = page; +} + +- (void)nextPageOrDismiss { + NSUInteger nextPage = self.pageControl.currentPage + 1; + if (nextPage < self.pageControl.numberOfPages) { + [self scrollTo:nextPage]; + } else { + [self dismissViewControllerAnimated:YES completion:self.completion]; + } +} + +#pragma mark - Targets + +- (void)pageControlValueChanged { + [self scrollTo:self.pageControl.currentPage]; +} + +#pragma mark - IBActions + +- (IBAction)primaryButtonTapped:(UIButton *)sender { + switch (self.type) { + case OnboardingTypeDefault: { + UINavigationController *createAccountNC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"CreateAccountNavigationControllerID"]; + [self presentViewController:createAccountNC animated:YES completion:nil]; + break; + } + + case OnboardingTypePermissions: { + if (self.scrollView.subviews.firstObject.subviews.count <= self.pageControl.currentPage) { + return; + } + + OnboardingView *currentView = self.scrollView.subviews.firstObject.subviews[self.pageControl.currentPage]; + switch (currentView.type) { + case OnboardingViewTypePhotosPermission: { + [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { + [self nextPageOrDismiss]; + }]; + break; + } + + case OnboardingViewTypeMicrophoneAndCameraPermissions: { + [DevicePermissionsHelper audioPermissionModal:NO forIncomingCall:NO withCompletionHandler:^(BOOL granted) { + [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { + [self nextPageOrDismiss]; + }]; + }]; + break; + } + + case OnboardingViewTypeNotificationsPermission: { + [DevicePermissionsHelper notificationsPermissionWithCompletionHandler:^(BOOL granted) { + [self nextPageOrDismiss]; + }]; + break; + } + + default: + [self nextPageOrDismiss]; + break; + } + break; + } + } +} + +- (IBAction)secondaryButtonTapped:(UIButton *)sender { + switch (self.type) { + case OnboardingTypeDefault: { + UINavigationController *loginNC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginNavigationControllerID"]; + [self presentViewController:loginNC animated:YES completion:nil]; + break; + } + + case OnboardingTypePermissions: + [self nextPageOrDismiss]; + break; + } +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + NSInteger newPage = scrollView.contentOffset.x / scrollView.frame.size.width; + self.pageControl.currentPage = newPage; +} + +@end diff --git a/iMEGA/Vendor/Karere b/iMEGA/Vendor/Karere index 9513c31474..6a0b764cbd 160000 --- a/iMEGA/Vendor/Karere +++ b/iMEGA/Vendor/Karere @@ -1 +1 @@ -Subproject commit 9513c3147484bcdb67bc986afbdbe1f1fbaa4ec6 +Subproject commit 6a0b764cbdb831e490bc287a50cb10a5b8b47cd3 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 9c5e0d9ce5..87f800820e 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 9c5e0d9ce5247add8f12151d90a9d9ff988b63c7 +Subproject commit 87f800820e6b5c95ccb2f26002f0f387438f5a4c From 3607fe174af036e77d3fb8c4e372a27132503fa7 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Tue, 18 Dec 2018 18:33:14 +0100 Subject: [PATCH 16/58] Request the account details if there is a possible significant change in the storage state (EventStorage - StorageStateChange) --- iMEGA/AppDelegate.m | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index e2eb580320..098110fdc9 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -1515,21 +1515,25 @@ - (void)onEvent:(MEGASdk *)api event:(MEGAEvent *)event { break; case EventStorage: { - [api getAccountDetails]; - static BOOL alreadyPresented = NO; - if (!alreadyPresented && (event.number == StorageStateRed || event.number == StorageStateOrange)) { - NSString *detail = event.number == StorageStateOrange ? AMLocalizedString(@"cloudDriveIsAlmostFull", @"Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are.") : AMLocalizedString(@"cloudDriveIsFull", @"A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text."); - detail = [detail mnz_removeWebclientFormatters]; - NSString *maxStorage = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7]]; - NSString *maxStorageTB = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7] / 1024]; - detail = [detail stringByReplacingOccurrencesOfString:@"4096" withString:maxStorage]; - detail = [detail stringByReplacingOccurrencesOfString:@"4" withString:maxStorageTB]; - alreadyPresented = YES; - NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); - UIImage *image = event.number == StorageStateOrange ? [UIImage imageNamed:@"storage_almost_full"] : [UIImage imageNamed:@"storage_full"]; - [self presentUpgradeViewControllerTitle:title detail:detail image:image]; + if (event.number == StorageStateChange) { + [api getAccountDetails]; + } else { + static BOOL alreadyPresented = NO; + if (!alreadyPresented && (event.number == StorageStateRed || event.number == StorageStateOrange)) { + NSString *detail = event.number == StorageStateOrange ? AMLocalizedString(@"cloudDriveIsAlmostFull", @"Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. Please leave the [S], [/S], [A], [/A] placeholders as they are.") : AMLocalizedString(@"cloudDriveIsFull", @"A message informing the user that they've reached the full capacity of their accounts. Please leave [S], [/S] as it is which is used to bolden the text."); + detail = [detail mnz_removeWebclientFormatters]; + NSString *maxStorage = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7]]; + NSString *maxStorageTB = [NSString stringWithFormat:@"%ld", (long)[[MEGAPurchase sharedInstance].pricing storageGBAtProductIndex:7] / 1024]; + detail = [detail stringByReplacingOccurrencesOfString:@"4096" withString:maxStorage]; + detail = [detail stringByReplacingOccurrencesOfString:@"4" withString:maxStorageTB]; + alreadyPresented = YES; + NSString *title = AMLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); + UIImage *image = event.number == StorageStateOrange ? [UIImage imageNamed:@"storage_almost_full"] : [UIImage imageNamed:@"storage_full"]; + [self presentUpgradeViewControllerTitle:title detail:detail image:image]; + } } } + break; default: From d09e9d8a7b87293477337674362a8384baffffcc Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Tue, 18 Dec 2018 18:57:37 +0100 Subject: [PATCH 17/58] Some UI fixes for iPad --- iMEGA/Launch/InitialLaunchViewController.m | 4 ++-- iMEGA/Launch/Launch.storyboard | 17 ++++++++--------- .../Onboarding.storyboard | 12 ++++++------ .../OnboardingViewController.m | 10 +++++++++- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index f8d6df7426..c315dc0624 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -28,8 +28,8 @@ - (void)viewDidLoad { - (void)performAnimation { [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.circularShapeLayer.hidden = YES; - CGFloat newXY = self.logoImageView.frame.origin.x; - self.logoImageView.frame = CGRectMake(newXY, newXY, self.logoImageView.frame.size.width, self.logoImageView.frame.size.height); + CGFloat newY = MIN(self.logoImageView.frame.origin.x, 145.0f); + self.logoImageView.frame = CGRectMake(self.logoImageView.frame.origin.x, newY, self.logoImageView.frame.size.width, self.logoImageView.frame.size.height); } completion:^(BOOL finished) { self.titleLabel.hidden = self.descriptionLabel.hidden = NO; self.setupButton.hidden = self.skipButton.hidden = NO; diff --git a/iMEGA/Launch/Launch.storyboard b/iMEGA/Launch/Launch.storyboard index ab119fd7a3..260aa4dd48 100644 --- a/iMEGA/Launch/Launch.storyboard +++ b/iMEGA/Launch/Launch.storyboard @@ -1,11 +1,10 @@ - + - - + @@ -160,10 +159,11 @@ @@ -136,9 +116,33 @@ - + + + + + + + + - @@ -146,19 +150,18 @@ - + + - - + - @@ -170,6 +173,7 @@ + @@ -179,15 +183,16 @@ + - + - + @@ -223,33 +228,6 @@ - - - - - - - - - - @@ -290,9 +268,40 @@ + + + + + + + + + + + + + + + - @@ -306,16 +315,13 @@ - - - + - @@ -332,6 +338,7 @@ + @@ -344,6 +351,7 @@ + From 8e8bb7bc881e8d02f5f3cd83d3fb21ddc4ed4822 Mon Sep 17 00:00:00 2001 From: Carlos Martin Acera Date: Tue, 8 Jan 2019 11:27:46 +0100 Subject: [PATCH 31/58] Folder links issues: - The custom actions menu header shows "Empty folder" in folder links although the folder has content (#11243) - Selection mode in folder links not updating the navigation title (#11244) --- iMEGA/Links/FolderLinkViewController.m | 32 +++++++++++++------ .../CustomActionViewController.m | 11 ++++--- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/iMEGA/Links/FolderLinkViewController.m b/iMEGA/Links/FolderLinkViewController.m index f858d580e8..205c77112e 100644 --- a/iMEGA/Links/FolderLinkViewController.m +++ b/iMEGA/Links/FolderLinkViewController.m @@ -35,8 +35,6 @@ @interface FolderLinkViewController () Date: Tue, 8 Jan 2019 11:27:54 +0100 Subject: [PATCH 32/58] Fix crash at MEGA: -[NotificationsTableViewController configureContentLabel:forAlert:] (11246) --- .../My Account/Notifications/NotificationsTableViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.m b/iMEGA/My Account/Notifications/NotificationsTableViewController.m index 2171a18f09..0462ec7854 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController.m +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.m @@ -222,7 +222,7 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypeDeletedShare: { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; if ([userAlert numberAtIndex:0] == 0) { - NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name attributes:@{ NSFontAttributeName : self.boldFont }]; + NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name ?: @"" attributes:@{ NSFontAttributeName : self.boldFont }]; NSString *text = AMLocalizedString(@"A user has left the shared folder {0}", @"notification text"); NSRange range = [text rangeOfString:@"{0}"]; NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; From 5693394c29648e8b6ee51c24d30079c9bb39f1ec Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Tue, 8 Jan 2019 12:47:11 +0100 Subject: [PATCH 33/58] Fix crash at Photos: -[PHInternalAssetResource initWithResourceType:fromAsset:managedAsset:] (11248) --- iMEGA/Transfers/TransferTableViewCell.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iMEGA/Transfers/TransferTableViewCell.m b/iMEGA/Transfers/TransferTableViewCell.m index f47199388f..916f32614b 100644 --- a/iMEGA/Transfers/TransferTableViewCell.m +++ b/iMEGA/Transfers/TransferTableViewCell.m @@ -96,6 +96,10 @@ - (void)configureCellForQueuedTransfer:(NSString *)uploadTransferLocalIdentifier } PHAsset *asset = fetchResult.firstObject; + if (asset == nil) { + return; + } + NSString *extension; if ([PHAssetResource assetResourcesForAsset:asset].count > 0) { From c72daec0fe5c8661155e0db90a2018f9e34c8e47 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Tue, 8 Jan 2019 13:27:06 +0100 Subject: [PATCH 34/58] Fixed UI issue with permissions views - Moved properties from the header to the implementation file. --- .../OnboardingView/OnboardingView.h | 5 ----- .../OnboardingView/OnboardingView.m | 17 +++++++++++++++++ .../OnboardingView/OnboardingView.xib | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h index a6f03502e3..5eb182dd75 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h @@ -8,13 +8,8 @@ NS_ASSUME_NONNULL_BEGIN IB_DESIGNABLE @interface OnboardingView : UIView -@property (nonatomic) UIView *customView; @property (nonatomic) OnboardingViewType type; -@property (weak, nonatomic) IBOutlet UIImageView *imageView; -@property (weak, nonatomic) IBOutlet UILabel *titleLabel; -@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; - @end NS_ASSUME_NONNULL_END diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m index 8916dc8549..697d320282 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m @@ -1,6 +1,17 @@ #import "OnboardingView.h" +@interface OnboardingView () + +@property (nonatomic) UIView *customView; + +@property (weak, nonatomic) IBOutlet UIImageView *imageView; +@property (weak, nonatomic) IBOutlet UILabel *titleLabel; +@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *imageViewLeadingConstraint; + +@end + @implementation OnboardingView - (instancetype)initWithFrame:(CGRect)frame { @@ -66,19 +77,25 @@ - (void)setType:(OnboardingViewType)type { break; case OnboardingViewTypePhotosPermission: + self.imageViewLeadingConstraint.active = NO; self.imageView.image = [UIImage imageNamed:@"photosPermission"]; + self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.titleLabel.text = AMLocalizedString(@"Allow Access to Photos", @"Title label that explains that the user is going to be asked for the photos permission"); self.descriptionLabel.text = AMLocalizedString(@"Please give the MEGA App permission to access Photos to share photos and videos.", @"Detailed explanation of why the user should give permission to access to the photos"); break; case OnboardingViewTypeMicrophoneAndCameraPermissions: + self.imageViewLeadingConstraint.active = NO; self.imageView.image = [UIImage imageNamed:@"groupChat"]; + self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.titleLabel.text = AMLocalizedString(@"Enable Microphone and Camera", @"Title label that explains that the user is going to be asked for the microphone and camera permission"); self.descriptionLabel.text = AMLocalizedString(@"To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", @"Detailed explanation of why the user should give permission to access to the camera and the microphone"); break; case OnboardingViewTypeNotificationsPermission: + self.imageViewLeadingConstraint.active = NO; self.imageView.image = [UIImage imageNamed:@"micAndCamPermission"]; + self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.titleLabel.text = AMLocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); self.descriptionLabel.text = AMLocalizedString(@"We would like to send you notifications so you receive new messages on your device instantly.", @"Detailed explanation of why the user should give permission to deliver notifications"); break; diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib index 9fdb558ecf..56392c3be1 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib @@ -13,6 +13,7 @@ + From 8b97c9a65ee606536af1fab2fe1a62ae7172e827 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Tue, 8 Jan 2019 14:18:54 +0100 Subject: [PATCH 35/58] When you get a contact (who is not your contact) sent through chat, you can only do something with that contact when long pressing (11249) --- iMEGA/Chat/MessagesViewController.m | 17 ++-- iMEGA/Contacts/ContactDetailsViewController.m | 86 ++++++++++++------- .../Navigation bar/add.imageset/Contents.json | 3 + 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index a8e8c4a3d3..fd16096c24 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -2178,15 +2178,14 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessage } else if (message.type == MEGAChatMessageTypeContact) { if (message.usersCount == 1) { NSString *userEmail = [message userEmailAtIndex:0]; - MEGAUser *user = [[MEGASdkManager sharedMEGASdk] contactForEmail:userEmail]; - if ((user != nil) && (user.visibility == MEGAUserVisibilityVisible)) { //It's one of your contacts, open 'Contact Info' view - ContactDetailsViewController *contactDetailsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactDetailsViewControllerID"]; - contactDetailsVC.contactDetailsMode = ContactDetailsModeDefault; - contactDetailsVC.userEmail = userEmail; - contactDetailsVC.userName = [message userNameAtIndex:0]; - contactDetailsVC.userHandle = [message userHandleAtIndex:0]; - [self.navigationController pushViewController:contactDetailsVC animated:YES]; - } + NSString *userName = [message userNameAtIndex:0]; + uint64_t userHandle = [message userHandleAtIndex:0]; + ContactDetailsViewController *contactDetailsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactDetailsViewControllerID"]; + contactDetailsVC.contactDetailsMode = ContactDetailsModeDefault; + contactDetailsVC.userEmail = userEmail; + contactDetailsVC.userName = userName; + contactDetailsVC.userHandle = userHandle; + [self.navigationController pushViewController:contactDetailsVC animated:YES]; } else { ChatAttachedContactsViewController *chatAttachedContactsVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"ChatAttachedContactsViewControllerID"]; chatAttachedContactsVC.message = message; diff --git a/iMEGA/Contacts/ContactDetailsViewController.m b/iMEGA/Contacts/ContactDetailsViewController.m index 13aeac5d3c..04ae64d3ec 100644 --- a/iMEGA/Contacts/ContactDetailsViewController.m +++ b/iMEGA/Contacts/ContactDetailsViewController.m @@ -5,6 +5,7 @@ #import "Helper.h" #import "UIImageView+MNZCategory.h" #import "NSString+MNZCategory.h" +#import "MEGAInviteContactRequestDelegate.h" #import "MEGANavigationController.h" #import "MEGANode+MNZCategory.h" #import "MEGARemoveContactRequestDelegate.h" @@ -61,7 +62,11 @@ - (void)viewDidLoad { self.chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:self.chatId]; [self.avatarImageView mnz_setImageForUserHandle:[self.chatRoom peerHandleAtIndex:0] name:self.chatRoom.title]; } else { - [self.avatarImageView mnz_setImageForUserHandle:self.user.handle]; + if (self.user) { + [self.avatarImageView mnz_setImageForUserHandle:self.user.handle]; + } else { + [self.avatarImageView mnz_setImageForUserHandle:self.userHandle name:self.userName]; + } } //TODO: Show the blue check if the Contact is verified @@ -226,7 +231,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger if (section == 0) { if (self.contactDetailsMode == ContactDetailsModeDefault) { //TODO: When possible, re-add the rows "Chat Notifications", "Set Nickname" and "Verify Credentials". - numberOfRows = 2; + if (self.user) { + numberOfRows = 2; + } else { + numberOfRows = 1; + } } else if (self.contactDetailsMode == ContactDetailsModeFromChat) { //TODO: When possible, re-add the rows "Chat Notifications", "Set Nickname" and "Verify Credentials". if (self.user.visibility == MEGAUserVisibilityHidden) { @@ -249,18 +258,24 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; if (self.contactDetailsMode == ContactDetailsModeDefault) { - switch (indexPath.row) { - case 0: //Send Message - cell.avatarImageView.image = [UIImage imageNamed:@"sendMessage"]; - cell.nameLabel.text = AMLocalizedString(@"sendMessage", @"Title to perform the action of sending a message to a contact."); - break; - - case 1: //Remove Contact - cell.avatarImageView.image = [UIImage imageNamed:@"delete"]; - cell.nameLabel.text = AMLocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts"); - cell.nameLabel.font = [UIFont mnz_SFUIRegularWithSize:15.0f]; - cell.nameLabel.textColor = UIColor.mnz_redMain; - break; + if (self.user) { + switch (indexPath.row) { + case 0: //Send Message + cell.avatarImageView.image = [UIImage imageNamed:@"sendMessage"]; + cell.nameLabel.text = AMLocalizedString(@"sendMessage", @"Title to perform the action of sending a message to a contact."); + break; + + case 1: //Remove Contact + cell.avatarImageView.image = [UIImage imageNamed:@"delete"]; + cell.nameLabel.text = AMLocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts"); + cell.nameLabel.font = [UIFont mnz_SFUIRegularWithSize:15.0f]; + cell.nameLabel.textColor = UIColor.mnz_redMain; + break; + } + } else { + cell.avatarImageView.image = [UIImage imageNamed:@"add"]; + cell.avatarImageView.tintColor = [UIColor mnz_gray777777]; + cell.nameLabel.text = AMLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); } } else if (self.contactDetailsMode == ContactDetailsModeFromChat) { switch (indexPath.row) { @@ -351,29 +366,34 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { if (self.contactDetailsMode == ContactDetailsModeDefault) { - switch (indexPath.row) { - case 0: { //Send Message - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"IsChatEnabled"]) { - MEGAChatRoom *chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomByUser:self.userHandle]; - if (chatRoom) { - [self changeToChatTabAndOpenChatId:chatRoom.chatId]; - } else { - MEGAChatPeerList *peerList = [[MEGAChatPeerList alloc] init]; - [peerList addPeerWithHandle:self.userHandle privilege:MEGAChatRoomPrivilegeStandard]; - MEGAChatCreateChatGroupRequestDelegate *createChatGroupRequestDelegate = [[MEGAChatCreateChatGroupRequestDelegate alloc] initWithCompletion:^(MEGAChatRoom *chatRoom) { + if (self.user) { + switch (indexPath.row) { + case 0: { //Send Message + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"IsChatEnabled"]) { + MEGAChatRoom *chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomByUser:self.userHandle]; + if (chatRoom) { [self changeToChatTabAndOpenChatId:chatRoom.chatId]; - }]; - [[MEGASdkManager sharedMEGAChatSdk] createChatGroup:NO peers:peerList delegate:createChatGroupRequestDelegate]; + } else { + MEGAChatPeerList *peerList = [[MEGAChatPeerList alloc] init]; + [peerList addPeerWithHandle:self.userHandle privilege:MEGAChatRoomPrivilegeStandard]; + MEGAChatCreateChatGroupRequestDelegate *createChatGroupRequestDelegate = [[MEGAChatCreateChatGroupRequestDelegate alloc] initWithCompletion:^(MEGAChatRoom *chatRoom) { + [self changeToChatTabAndOpenChatId:chatRoom.chatId]; + }]; + [[MEGASdkManager sharedMEGAChatSdk] createChatGroup:NO peers:peerList delegate:createChatGroupRequestDelegate]; + } + } else { + [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:AMLocalizedString(@"chatIsDisabled", @"Title show when the chat is disabled")]; } - } else { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:AMLocalizedString(@"chatIsDisabled", @"Title show when the chat is disabled")]; + break; } - break; + + case 1: //Remove Contact + [self showRemoveContactAlert]; + break; } - - case 1: //Remove Contact - [self showRemoveContactAlert]; - break; + } else { + MEGAInviteContactRequestDelegate *inviteContactRequestDelegate = [[MEGAInviteContactRequestDelegate alloc] initWithNumberOfRequests:1]; + [[MEGASdkManager sharedMEGASdk] inviteContactWithEmail:self.userEmail message:@"" action:MEGAInviteActionAdd delegate:inviteContactRequestDelegate]; } } else if (self.contactDetailsMode == ContactDetailsModeFromChat) { switch (indexPath.row) { diff --git a/iMEGA/Images.xcassets/Navigation bar/add.imageset/Contents.json b/iMEGA/Images.xcassets/Navigation bar/add.imageset/Contents.json index 2e2e9b52c0..e8b9b294cd 100644 --- a/iMEGA/Images.xcassets/Navigation bar/add.imageset/Contents.json +++ b/iMEGA/Images.xcassets/Navigation bar/add.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file From 6bdae5dbc904dfc748a29db50ffb47c2d242a0f2 Mon Sep 17 00:00:00 2001 From: Javier Santana Espejo Date: Tue, 8 Jan 2019 16:00:34 +0100 Subject: [PATCH 36/58] Added missing safe area layout guides - Added on Photos, Chat, Links and My Account storyboards. - Also modified some constraints on the Chat storyboard and its xibs to comply with the safe areas (JSQMessagesViewController submodule update 0f5e4a27a93ad1386068c2260eeec681b1842618). - Fixed typo on the MEGAMessagesTypingIndicatorFooterView. --- MEGA.xcodeproj/project.pbxproj | 20 +- iMEGA/Camera uploads/Photos.storyboard | 38 ++- iMEGA/Chat/Chat.storyboard | 153 ++++++----- iMEGA/Chat/MEGAOpenMessageHeaderView.xib | 12 +- iMEGA/Chat/MessagesViewController.m | 10 +- .../Chat/Views/MEGAMessageAttachmentView.xib | 6 +- .../MEGAMessagesTypingIndicatorFoorterView.m | 18 -- ...> MEGAMessagesTypingIndicatorFooterView.h} | 2 +- .../MEGAMessagesTypingIndicatorFooterView.m | 18 ++ ...MEGAMessagesTypingIndicatorFooterView.xib} | 19 +- iMEGA/Links/Links.storyboard | 31 +-- iMEGA/My Account/MyAccount.storyboard | 245 ++++++++---------- iMEGA/Vendor/JSQMessagesViewController | 2 +- 13 files changed, 274 insertions(+), 300 deletions(-) delete mode 100644 iMEGA/Chat/Views/MEGAMessagesTypingIndicatorFoorterView.m rename iMEGA/Chat/Views/{MEGAMessagesTypingIndicatorFoorterView.h => MEGAMessagesTypingIndicatorFooterView.h} (56%) create mode 100644 iMEGA/Chat/Views/MEGAMessagesTypingIndicatorFooterView.m rename iMEGA/Chat/Views/{MEGAMessagesTypingIndicatorFoorterView.xib => MEGAMessagesTypingIndicatorFooterView.xib} (70%) diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index fd91f82837..59e56d5727 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -11,8 +11,8 @@ 137361931A6664C300B740E8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 137361921A6664C300B740E8 /* AppDelegate.m */; }; 1373619E1A6664C300B740E8 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1373619D1A6664C300B740E8 /* Images.xcassets */; }; 410AD3C11C883AA900BF939E /* MEGAAVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 410AD3C01C883AA900BF939E /* MEGAAVViewController.m */; }; - 41136FBC1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m */; }; - 41136FBE1E1693410031C516 /* MEGAMessagesTypingIndicatorFoorterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFoorterView.xib */; }; + 41136FBC1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.m */; }; + 41136FBE1E1693410031C516 /* MEGAMessagesTypingIndicatorFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFooterView.xib */; }; 411504941CEB183600BF38E3 /* SaveToCameraRollActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 411504931CEB183600BF38E3 /* SaveToCameraRollActivity.m */; }; 4115D6441E718B1F004BB0FF /* UIImage+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 4115D6431E718B1F004BB0FF /* UIImage+MNZCategory.m */; }; 411C8B6B1DECBD87006A0D8C /* MEGAUser+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 411C8B6A1DECBD87006A0D8C /* MEGAUser+MNZCategory.m */; }; @@ -796,9 +796,9 @@ 410333FA1B9995DC00380FF3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; 410AD3BF1C883AA900BF939E /* MEGAAVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAAVViewController.h; sourceTree = ""; }; 410AD3C01C883AA900BF939E /* MEGAAVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGAAVViewController.m; sourceTree = ""; }; - 41136FBA1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAMessagesTypingIndicatorFoorterView.h; sourceTree = ""; }; - 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGAMessagesTypingIndicatorFoorterView.m; sourceTree = ""; }; - 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFoorterView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MEGAMessagesTypingIndicatorFoorterView.xib; sourceTree = ""; }; + 41136FBA1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAMessagesTypingIndicatorFooterView.h; sourceTree = ""; }; + 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGAMessagesTypingIndicatorFooterView.m; sourceTree = ""; }; + 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFooterView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MEGAMessagesTypingIndicatorFooterView.xib; sourceTree = ""; }; 411504921CEB183600BF38E3 /* SaveToCameraRollActivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveToCameraRollActivity.h; sourceTree = ""; }; 411504931CEB183600BF38E3 /* SaveToCameraRollActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveToCameraRollActivity.m; sourceTree = ""; }; 4115D6421E718B1F004BB0FF /* UIImage+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+MNZCategory.h"; sourceTree = ""; }; @@ -1906,9 +1906,9 @@ 41136FB91E1692650031C516 /* Views */ = { isa = PBXGroup; children = ( - 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFoorterView.xib */, - 41136FBA1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.h */, - 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m */, + 41136FBA1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.h */, + 41136FBB1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.m */, + 41136FBD1E1693410031C516 /* MEGAMessagesTypingIndicatorFooterView.xib */, E8E3E6F61E0939BE00D45F45 /* MEGAOpenMessageHeaderView.h */, E8E3E6F71E0939BE00D45F45 /* MEGAOpenMessageHeaderView.m */, E8E3E6F81E0939BE00D45F45 /* MEGAOpenMessageHeaderView.xib */, @@ -3757,7 +3757,7 @@ 4172795C1DB663FA001AC818 /* JSQMessagesToolbarContentView.xib in Resources */, 4172795E1DB663FA001AC818 /* JSQMessagesTypingIndicatorFooterView.xib in Resources */, 5B5B50011E684FDC00DBEB3B /* SF-UI-Display-Regular.otf in Resources */, - 41136FBE1E1693410031C516 /* MEGAMessagesTypingIndicatorFoorterView.xib in Resources */, + 41136FBE1E1693410031C516 /* MEGAMessagesTypingIndicatorFooterView.xib in Resources */, 83D64E2D20178A0400E24155 /* PasswordReminder.storyboard in Resources */, A82E5BC81F3465A3003C149C /* Spotlight_file@2x.png in Resources */, 5B6429E02020A8CF000E0DCB /* SF-UI-Text-Semibold.otf in Resources */, @@ -4209,7 +4209,7 @@ A81613FE1EDC1CA900500381 /* MEGACreateAccountRequestDelegate.m in Sources */, 41EAAE4E1D06B49400622FAF /* UIImage+CTAssetsPickerController.m in Sources */, A820DA3C1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m in Sources */, - 41136FBC1E1692A90031C516 /* MEGAMessagesTypingIndicatorFoorterView.m in Sources */, + 41136FBC1E1692A90031C516 /* MEGAMessagesTypingIndicatorFooterView.m in Sources */, 94F376E71FE7C126009AAA10 /* MEGAToolbarAssetPicker.m in Sources */, 41FC83C01AAE056C008FA551 /* CameraUploadsTableViewController.m in Sources */, 944B205D1EE55533003C967B /* UIAlertAction+MNZCategory.m in Sources */, diff --git a/iMEGA/Camera uploads/Photos.storyboard b/iMEGA/Camera uploads/Photos.storyboard index ec568bfa65..848bded5e6 100644 --- a/iMEGA/Camera uploads/Photos.storyboard +++ b/iMEGA/Camera uploads/Photos.storyboard @@ -1,11 +1,12 @@ - + - + + @@ -18,10 +19,6 @@ - - - - @@ -195,17 +192,18 @@ - - - - - - + + + + + + - + - + + @@ -237,6 +235,7 @@ + @@ -308,10 +307,6 @@ - - - - @@ -404,20 +399,21 @@ - + - + - + + diff --git a/iMEGA/Chat/Chat.storyboard b/iMEGA/Chat/Chat.storyboard index dbfd3d58ab..85820e371e 100644 --- a/iMEGA/Chat/Chat.storyboard +++ b/iMEGA/Chat/Chat.storyboard @@ -39,10 +39,10 @@ - + - + @@ -50,19 +50,19 @@ - + - + - + @@ -75,11 +75,11 @@ - + - + @@ -251,7 +251,7 @@ - + @@ -285,7 +285,6 @@ - @@ -304,13 +303,13 @@ - + @@ -326,7 +325,7 @@ diff --git a/iMEGA/Links/Links.storyboard b/iMEGA/Links/Links.storyboard index 24c99a01c2..31e6e1f46d 100644 --- a/iMEGA/Links/Links.storyboard +++ b/iMEGA/Links/Links.storyboard @@ -1,11 +1,12 @@ - + + @@ -24,10 +25,6 @@ - - - - @@ -366,11 +363,12 @@ - - - - + + + + + @@ -405,6 +403,7 @@ + @@ -445,10 +444,6 @@ - - - - @@ -533,12 +528,13 @@ - - - + + + - + + @@ -572,6 +568,7 @@ + diff --git a/iMEGA/My Account/MyAccount.storyboard b/iMEGA/My Account/MyAccount.storyboard index 960484b5a2..28deab7c09 100644 --- a/iMEGA/My Account/MyAccount.storyboard +++ b/iMEGA/My Account/MyAccount.storyboard @@ -1,11 +1,12 @@ - + - + + @@ -47,10 +48,6 @@ - - - - @@ -68,13 +65,13 @@ - + - - - - - - - - + + + + + + + + + @@ -2225,6 +2208,7 @@ + @@ -2252,10 +2236,6 @@ - - - - @@ -2269,7 +2249,7 @@ - + @@ -2279,20 +2259,20 @@ - + @@ -2315,22 +2295,22 @@ - + - + - + - + @@ -2463,7 +2443,7 @@ + @@ -2651,10 +2632,6 @@ - - - - @@ -2755,15 +2732,16 @@ - - - - - + + + + + - - + + + @@ -2787,10 +2765,6 @@ - - - - @@ -2812,7 +2786,7 @@ + + + + + + + + + + + + + + + - @@ -306,16 +315,13 @@ - - - + - @@ -332,6 +338,7 @@ + @@ -344,6 +351,7 @@ + diff --git a/iMEGA/Login/CreateAccountViewController.m b/iMEGA/Login/CreateAccountViewController.m index 65adc7bf9c..3bafe79e6c 100644 --- a/iMEGA/Login/CreateAccountViewController.m +++ b/iMEGA/Login/CreateAccountViewController.m @@ -386,31 +386,25 @@ - (void)textFieldDidEndEditing:(UITextField *)textField { - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSString *text = [textField.text stringByReplacingCharactersInRange:range withString:string]; - BOOL createAccountEnabled = NO; switch (textField.tag) { case FirstNameTextFieldTag: - createAccountEnabled = !text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; [self.firstNameInputView setErrorState:NO withText:AMLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; break; case LastNameTextFieldTag: - createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; [self.lastNameInputView setErrorState:NO withText:AMLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; break; case EmailTextFieldTag: - createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; [self.emailInputView setErrorState:NO withText:AMLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; break; case PasswordTextFieldTag: - createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !text.mnz_isEmpty && [text isEqualToString:self.retypePasswordView.passwordTextField.text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; [self.passwordView setErrorState:NO withText:AMLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; break; case RetypeTextFieldTag: - createAccountEnabled = !self.firstNameInputView.inputTextField.text.mnz_isEmpty && !self.lastNameInputView.inputTextField.text.mnz_isEmpty && self.emailInputView.inputTextField.text.mnz_isValidEmail && !self.passwordView.passwordTextField.text.mnz_isEmpty && [self.passwordView.passwordTextField.text isEqualToString:text] && [[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] > PasswordStrengthVeryWeak && self.termsCheckboxButton.selected; [self.retypePasswordView setErrorState:NO withText:AMLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; break; diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index 3ac7becb6a..14360d051d 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -256,7 +256,7 @@ - (void)presentRingingCall:(MEGAChatSdk *)api call:(MEGAChatCall *)call { localNotification.alertTitle = @"MEGA"; localNotification.soundName = @"incoming_voice_video_call_iOS9.mp3"; localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:0]; - localNotification.alertBody = [NSString stringWithFormat:@"%@: %@", chatRoom.title, AMLocalizedString(@"calling...", @"Label shown when you receive an incoming call, before start the call.")]; + localNotification.alertBody = [NSString stringWithFormat:@"%@: %@", chatRoom.title, AMLocalizedString(@"Incoming call", @"notification subtitle of incoming calls")]; localNotification.userInfo = @{@"chatId" : @(call.chatId), @"callId" : @(call.callId) }; @@ -306,6 +306,8 @@ - (void)onChatListItemUpdate:(MEGAChatSdk *)api item:(MEGAChatListItem *)item { [messagesViewController updateUnreadLabel]; } } + } else if (item.changes == MEGAChatListItemChangeTypeArchived && item.unreadCount) { + [UIApplication sharedApplication].applicationIconBadgeNumber = api.unreadChats; } } diff --git a/iMEGA/My Account/MyAccountHallViewController.m b/iMEGA/My Account/MyAccountHallViewController.m index cbabd354bd..a15baecb2f 100644 --- a/iMEGA/My Account/MyAccountHallViewController.m +++ b/iMEGA/My Account/MyAccountHallViewController.m @@ -71,11 +71,17 @@ - (void)viewDidLoad { [_numberFormatter setMaximumFractionDigits:0]; MEGAContactLinkCreateRequestDelegate *delegate = [[MEGAContactLinkCreateRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { - NSString *destination = [NSString stringWithFormat:@"https://mega.nz/C!%@", [MEGASdk base64HandleForHandle:request.nodeHandle]]; - self.qrCodeImageView.image = [UIImage mnz_qrImageWithDotsFromString:destination withSize:self.qrCodeImageView.frame.size color:UIColor.mnz_redMain]; - self.avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor; - self.avatarImageView.layer.borderWidth = 6.0f; - self.avatarImageView.layer.cornerRadius = 40.0f; + CGSize qrImageSie = self.qrCodeImageView.frame.size; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + NSString *destination = [NSString stringWithFormat:@"https://mega.nz/C!%@", [MEGASdk base64HandleForHandle:request.nodeHandle]]; + UIImage *image = [UIImage mnz_qrImageWithDotsFromString:destination withSize:qrImageSie color:UIColor.mnz_redMain]; + dispatch_async(dispatch_get_main_queue(), ^{ + self.qrCodeImageView.image = image; + self.avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor; + self.avatarImageView.layer.borderWidth = 6.0f; + self.avatarImageView.layer.cornerRadius = 40.0f; + }); + }); }]; [[MEGASdkManager sharedMEGASdk] contactLinkCreateRenew:NO delegate:delegate]; diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.m b/iMEGA/My Account/Notifications/NotificationsTableViewController.m index 8dc6ece8ee..0462ec7854 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController.m +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.m @@ -221,8 +221,8 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypeDeletedShare: { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; - if (node && ![userAlert.path hasPrefix:userAlert.email]) { - NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name attributes:@{ NSFontAttributeName : self.boldFont }]; + if ([userAlert numberAtIndex:0] == 0) { + NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name ?: @"" attributes:@{ NSFontAttributeName : self.boldFont }]; NSString *text = AMLocalizedString(@"A user has left the shared folder {0}", @"notification text"); NSRange range = [text rangeOfString:@"{0}"]; NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; diff --git a/iMEGA/Offline/Offline.storyboard b/iMEGA/Offline/Offline.storyboard index 0390529295..32b778a96f 100644 --- a/iMEGA/Offline/Offline.storyboard +++ b/iMEGA/Offline/Offline.storyboard @@ -229,7 +229,7 @@ - + diff --git a/iMEGA/Offline/OfflineViewController.m b/iMEGA/Offline/OfflineViewController.m index add4f7c7cb..640425ca60 100644 --- a/iMEGA/Offline/OfflineViewController.m +++ b/iMEGA/Offline/OfflineViewController.m @@ -5,6 +5,7 @@ #import "UIScrollView+EmptyDataSet.h" #import "NSString+MNZCategory.h" #import "NSFileManager+MNZCategory.h" +#import "UIApplication+MNZCategory.h" #import "MEGANavigationController.h" #import "MEGASdkManager.h" @@ -29,7 +30,7 @@ static NSString *kFileSize = @"kFileSize"; static NSString *kisDirectory = @"kisDirectory"; -@interface OfflineViewController () { +@interface OfflineViewController () { } @property (weak, nonatomic) IBOutlet UIView *containerView; @@ -72,17 +73,14 @@ - (void)viewDidLoad { [self.navigationItem setTitle:self.folderPathFromOffline.lastPathComponent]; } - UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; - - [self.toolbar setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 49)]; - [self.toolbar setItems:@[self.activityBarButtonItem, flexibleItem, self.deleteBarButtonItem]]; - self.editBarButtonItem.title = AMLocalizedString(@"edit", @"Caption of a button to edit the files that are selected"); self.navigationItem.rightBarButtonItem = self.moreBarButtonItem; self.definesPresentationContext = YES; [self.view addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]]; + + self.searchController.delegate = self; } - (void)viewWillAppear:(BOOL)animated { @@ -138,6 +136,15 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id context) { [self.offlineTableView.tableView reloadEmptyDataSet]; + if (self.searchController.active) { + if (UIDevice.currentDevice.iPad) { + if (self != UIApplication.mnz_visibleViewController) { + [Helper resetSearchControllerFrame:self.searchController]; + } + } else { + [Helper resetSearchControllerFrame:self.searchController]; + } + } } completion:nil]; } @@ -745,13 +752,9 @@ - (void)itemTapped:(NSString *)name atIndexPath:(NSIndexPath *)indexPath { OfflineViewController *offlineVC = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineViewControllerID"]; [offlineVC setFolderPathFromOffline:folderPathFromOffline]; - if (self.searchController.isActive) { - [self.searchController dismissViewControllerAnimated:YES completion:^{ - [self.navigationController pushViewController:offlineVC animated:YES]; - }]; - } else { - [self.navigationController pushViewController:offlineVC animated:YES]; - } + + [self.navigationController pushViewController:offlineVC animated:YES]; + } else if (self.previewDocumentPath.mnz_isMultimediaPathExtension) { AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:self.previewDocumentPath]]; @@ -788,11 +791,19 @@ - (void)setViewEditing:(BOOL)editing { [self updateNavigationBarTitle]; if (editing) { + UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; + self.toolbar.items = @[self.activityBarButtonItem, flexibleItem, self.deleteBarButtonItem]; + self.navigationItem.rightBarButtonItem = self.editBarButtonItem; self.editBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; [self.toolbar setAlpha:0.0]; - [self.tabBarController.tabBar addSubview:self.toolbar]; + [self.tabBarController.view addSubview:self.toolbar]; + self.toolbar.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[[self.toolbar.topAnchor constraintEqualToAnchor:self.tabBarController.tabBar.topAnchor constant:0], + [self.toolbar.leadingAnchor constraintEqualToAnchor:self.tabBarController.tabBar.leadingAnchor constant:0], + [self.toolbar.trailingAnchor constraintEqualToAnchor:self.tabBarController.tabBar.trailingAnchor constant:0], + [self.toolbar.heightAnchor constraintEqualToConstant:49.0]]]; [UIView animateWithDuration:0.33f animations:^ { [self.toolbar setAlpha:1.0]; }]; @@ -933,6 +944,14 @@ - (void)updateSearchResultsForSearchController:(UISearchController *)searchContr [self reloadData]; } +#pragma mark - UISearchControllerDelegate + +- (void)didPresentSearchController:(UISearchController *)searchController { + if (UIDevice.currentDevice.iPhoneDevice && UIDeviceOrientationIsLandscape(UIDevice.currentDevice.orientation)) { + [Helper resetSearchControllerFrame:searchController]; + } +} + #pragma mark - UILongPressGestureRecognizer - (void)longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer { diff --git a/iMEGA/Settings/About/AboutTableViewController.m b/iMEGA/Settings/About/AboutTableViewController.m index 0cdaff0fe6..15327b6e29 100644 --- a/iMEGA/Settings/About/AboutTableViewController.m +++ b/iMEGA/Settings/About/AboutTableViewController.m @@ -39,7 +39,7 @@ - (void)viewDidLoad { self.versionCell.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; self.sdkVersionLabel.text = AMLocalizedString(@"sdkVersion", @"Title of the label where the SDK version is shown"); - self.sdkVersionSHALabel.text = @"08784688"; + self.sdkVersionSHALabel.text = @"4ca0a05e"; self.megachatSdkVersionLabel.text = AMLocalizedString(@"megachatSdkVersion", @"Title of the label where the MEGAchat SDK version is shown"); self.megachatSdkSHALabel.text = @"9c9b9cce"; diff --git a/iMEGA/Settings/Advanced/AdvancedTableViewController.m b/iMEGA/Settings/Advanced/AdvancedTableViewController.m index 95092982a7..8820238710 100644 --- a/iMEGA/Settings/Advanced/AdvancedTableViewController.m +++ b/iMEGA/Settings/Advanced/AdvancedTableViewController.m @@ -133,9 +133,12 @@ - (IBAction)videosSwitchValueChanged:(UISwitch *)sender { - (IBAction)mediaInGallerySwitchChanged:(UISwitch *)sender { [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { - if (granted) { - [[NSUserDefaults standardUserDefaults] setBool:self.saveMediaInGallerySwitch.isOn forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + if (!granted && self.saveMediaInGallerySwitch.isOn) { + [self.saveMediaInGallerySwitch setOn:NO animated:YES]; + [DevicePermissionsHelper alertPhotosPermission]; + } else { + [NSUserDefaults.standardUserDefaults setBool:self.saveMediaInGallerySwitch.isOn forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [NSUserDefaults.standardUserDefaults synchronize]; } }]; } diff --git a/iMEGA/Shared Items/SharedItemsViewController.m b/iMEGA/Shared Items/SharedItemsViewController.m index 395df33d16..5cb702949c 100644 --- a/iMEGA/Shared Items/SharedItemsViewController.m +++ b/iMEGA/Shared Items/SharedItemsViewController.m @@ -92,8 +92,6 @@ - (void)viewDidLoad { [self.view addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]]; - [self.toolbar setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 49)]; - self.searchController = [Helper customSearchControllerWithSearchResultsUpdaterDelegate:self searchBarDelegate:self]; self.tableView.tableHeaderView = self.searchController.searchBar; self.searchController.hidesNavigationBarDuringPresentation = NO; @@ -405,7 +403,13 @@ - (void)setEditing:(BOOL)editing animated:(BOOL)animated { self.editBarButtonItem.title = AMLocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; [self.toolbar setAlpha:0.0]; - [self.tabBarController.tabBar addSubview:self.toolbar]; + [self.tabBarController.view addSubview:self.toolbar]; + self.toolbar.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[[self.toolbar.topAnchor constraintEqualToAnchor:self.tabBarController.tabBar.topAnchor constant:0], + [self.toolbar.leadingAnchor constraintEqualToAnchor:self.tabBarController.tabBar.leadingAnchor constant:0], + [self.toolbar.trailingAnchor constraintEqualToAnchor:self.tabBarController.tabBar.trailingAnchor constant:0], + [self.toolbar.heightAnchor constraintEqualToConstant:49.0]]]; + [UIView animateWithDuration:0.33f animations:^ { [self.toolbar setAlpha:1.0]; }]; diff --git a/iMEGA/Transfers/TransferTableViewCell.m b/iMEGA/Transfers/TransferTableViewCell.m index e45c069e93..916f32614b 100644 --- a/iMEGA/Transfers/TransferTableViewCell.m +++ b/iMEGA/Transfers/TransferTableViewCell.m @@ -44,20 +44,7 @@ - (void)configureCellForTransfer:(MEGATransfer *)transfer delegate:(id 0) { + PHAssetResource *assetResource = [PHAssetResource assetResourcesForAsset:asset].firstObject; + if (assetResource.originalFilename) { + extension = assetResource.originalFilename.mnz_lastExtensionInLowercase; + } + } + + NSString *name = [NSString mnz_fileNameWithDate:asset.creationDate]; + if (extension) { + name = [name stringByAppendingPathExtension:extension]; + } + self.nameLabel.text = name; PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; @@ -120,7 +124,7 @@ - (void)configureCellForQueuedTransfer:(NSString *)uploadTransferLocalIdentifier if (result) { self.iconImageView.image = result; } else { - [self.iconImageView mnz_setImageForExtension:assetResource.originalFilename.pathExtension]; + [self.iconImageView mnz_setImageForExtension:extension]; } }]; diff --git a/iMEGA/Transfers/TransfersViewController.m b/iMEGA/Transfers/TransfersViewController.m index eef1200615..1e60108f7f 100644 --- a/iMEGA/Transfers/TransfersViewController.m +++ b/iMEGA/Transfers/TransfersViewController.m @@ -381,7 +381,7 @@ - (void)deleteUploadQueuedTransferWithLocalIdentifier:(NSString *)localIdentifie } } -- (void)deleteUploadTransfer:(MEGATransfer *)transfer { +- (void)deleteUploadingTransfer:(MEGATransfer *)transfer { NSIndexPath *indexPath = [self indexPathForTransfer:transfer]; if (indexPath) { [self.tableView beginUpdates]; @@ -655,7 +655,7 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: } } - [self deleteUploadTransfer:transfer]; + [self deleteUploadingTransfer:transfer]; } #pragma mark - TransferTableViewCellDelegate diff --git a/iMEGA/Cloud drive/CloudDriveGridFlowLayout.h b/iMEGA/Utils/Flow Layouts/GridFlowLayout.h similarity index 55% rename from iMEGA/Cloud drive/CloudDriveGridFlowLayout.h rename to iMEGA/Utils/Flow Layouts/GridFlowLayout.h index 430dd3b08b..7194ac44be 100644 --- a/iMEGA/Cloud drive/CloudDriveGridFlowLayout.h +++ b/iMEGA/Utils/Flow Layouts/GridFlowLayout.h @@ -3,7 +3,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface CloudDriveGridFlowLayout : UICollectionViewFlowLayout +@interface GridFlowLayout : UICollectionViewFlowLayout @end diff --git a/iMEGA/Cloud drive/CloudDriveGridFlowLayout.m b/iMEGA/Utils/Flow Layouts/GridFlowLayout.m similarity index 94% rename from iMEGA/Cloud drive/CloudDriveGridFlowLayout.m rename to iMEGA/Utils/Flow Layouts/GridFlowLayout.m index c17dae0320..c3dcec3e87 100644 --- a/iMEGA/Cloud drive/CloudDriveGridFlowLayout.m +++ b/iMEGA/Utils/Flow Layouts/GridFlowLayout.m @@ -1,7 +1,7 @@ -#import "CloudDriveGridFlowLayout.h" +#import "GridFlowLayout.h" -@implementation CloudDriveGridFlowLayout +@implementation GridFlowLayout - (void)prepareLayout { [super prepareLayout]; diff --git a/iMEGA/Utils/Helper.h b/iMEGA/Utils/Helper.h index 84d7a60f40..abf39e5df4 100644 --- a/iMEGA/Utils/Helper.h +++ b/iMEGA/Utils/Helper.h @@ -110,7 +110,8 @@ typedef NS_OPTIONS(NSUInteger, NodesAre) { + (UILabel *)customNavigationBarLabelWithTitle:(NSString *)title subtitle:(NSString *)subtitle color:(UIColor *)color; + (UISearchController *)customSearchControllerWithSearchResultsUpdaterDelegate:(id)searchResultsUpdaterDelegate searchBarDelegate:(id)searchBarDelegate; - ++ (void)resetSearchControllerFrame:(UISearchController *)searchController; + + (void)showExportMasterKeyInView:(UIViewController *)viewController completion:(void (^ __nullable)(void))completion; + (void)showMasterKeyCopiedAlert; diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index a4db0236ca..ee515020c7 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -614,7 +614,9 @@ + (void)startUploadTransfer:(MOUploadTransfer *)uploadTransfer { [[MEGASdkManager sharedMEGASdk] startUploadWithLocalPath:filePath.mnz_relativeLocalPath parent:parentNode appData:appData isSourceTemporary:YES]; } - [[Helper uploadingNodes] addObject:uploadTransfer.localIdentifier]; + if (uploadTransfer.localIdentifier) { + [[Helper uploadingNodes] addObject:uploadTransfer.localIdentifier]; + } [[MEGAStore shareInstance] deleteUploadTransfer:uploadTransfer]; } node:^(MEGANode *node) { if ([[[MEGASdkManager sharedMEGASdk] parentNodeForNode:node] handle] == parentNode.handle) { @@ -758,7 +760,9 @@ + (void)setThumbnailForNode:(MEGANode *)node api:(MEGASdk *)api cell:(id)cell re } if (reindex) { - [indexer index:node]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [indexer index:node]; + }); } } @@ -1165,6 +1169,12 @@ + (UISearchController *)customSearchControllerWithSearchResultsUpdaterDelegate:( return searchController; } ++ (void)resetSearchControllerFrame:(UISearchController *)searchController { + searchController.view.frame = CGRectMake(0, UIApplication.sharedApplication.statusBarFrame.size.height, searchController.view.frame.size.width, searchController.view.frame.size.height); + searchController.searchBar.superview.frame = CGRectMake(0, 0, searchController.searchBar.superview.frame.size.width, searchController.searchBar.superview.frame.size.height); + searchController.searchBar.frame = CGRectMake(0, 0, searchController.searchBar.frame.size.width, searchController.searchBar.frame.size.height); +} + #pragma mark - Manage session + (BOOL)hasSession_alertIfNot { diff --git a/iMEGA/Utils/MEGAAVViewController.m b/iMEGA/Utils/MEGAAVViewController.m index 01350ccba6..ca2135262f 100644 --- a/iMEGA/Utils/MEGAAVViewController.m +++ b/iMEGA/Utils/MEGAAVViewController.m @@ -66,6 +66,9 @@ - (void)viewDidLoad { selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; + [[AVAudioSession sharedInstance] setActive:YES error:nil]; } - (void)viewDidAppear:(BOOL)animated { @@ -112,6 +115,9 @@ - (void)viewDidDisappear:(BOOL)animated { [self stopStreaming]; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; + [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"presentPasscodeLater"] && [LTHPasscodeViewController doesPasscodeExist]) { [[LTHPasscodeViewController sharedUser] showLockScreenOver:UIApplication.mnz_presentingViewController.view withAnimation:YES diff --git a/iMEGA/Utils/MEGALinkManager.m b/iMEGA/Utils/MEGALinkManager.m index 1af5d4f9de..35e0545ab8 100644 --- a/iMEGA/Utils/MEGALinkManager.m +++ b/iMEGA/Utils/MEGALinkManager.m @@ -131,14 +131,9 @@ + (void)processSelectedOptionOnLink { if (![Helper isFreeSpaceEnoughToDownloadNode:node isFolderLink:NO]) { return; } - - if ([UIApplication.sharedApplication.keyWindow.rootViewController isKindOfClass:MainTabBarController.class]) { - MainTabBarController *mainTBC = (MainTabBarController *)UIApplication.sharedApplication.keyWindow.rootViewController; - [mainTBC showOffline]; - [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", @"Message shown when a download starts")]; - [Helper downloadNode:node folderPath:Helper.relativePathForOffline isFolderLink:NO shouldOverwrite:NO]; - } + [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", @"Message shown when a download starts")]; + [Helper downloadNode:node folderPath:Helper.relativePathForOffline isFolderLink:NO shouldOverwrite:NO]; break; } @@ -158,15 +153,10 @@ + (void)processSelectedOptionOnLink { return; } } - - if ([UIApplication.sharedApplication.keyWindow.rootViewController isKindOfClass:MainTabBarController.class]) { - MainTabBarController *mainTBC = (MainTabBarController *)UIApplication.sharedApplication.keyWindow.rootViewController; - [mainTBC showOffline]; - [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", @"Message shown when a download starts")]; - for (MEGANode *node in MEGALinkManager.nodesFromLinkMutableArray) { - [Helper downloadNode:node folderPath:Helper.relativePathForOffline isFolderLink:YES shouldOverwrite:NO]; - } + [SVProgressHUD showImage:[UIImage imageNamed:@"hudDownload"] status:AMLocalizedString(@"downloadStarted", @"Message shown when a download starts")]; + for (MEGANode *node in MEGALinkManager.nodesFromLinkMutableArray) { + [Helper downloadNode:node folderPath:Helper.relativePathForOffline isFolderLink:YES shouldOverwrite:NO]; } break; } diff --git a/iMEGA/Utils/MEGAProcessAsset.m b/iMEGA/Utils/MEGAProcessAsset.m index 3c4713ea03..dbcf8a8d38 100644 --- a/iMEGA/Utils/MEGAProcessAsset.m +++ b/iMEGA/Utils/MEGAProcessAsset.m @@ -24,13 +24,13 @@ @interface MEGAProcessAsset () @property (nonatomic, copy) void (^error)(NSError *error); @property (nonatomic, strong) MEGANode *parentNode; -@property (nonatomic, copy) NSMutableArray *assets; +@property (nonatomic, strong) NSMutableArray *assets; @property (nonatomic, copy) void (^filePaths)(NSArray *filePaths); @property (nonatomic, copy) void (^nodes)(NSArray *nodes); @property (nonatomic, copy) void (^errors)(NSArray *errors); -@property (nonatomic, copy) NSMutableArray *filePathsArray; -@property (nonatomic, copy) NSMutableArray *nodesArray; -@property (nonatomic, copy) NSMutableArray *errorsArray; +@property (nonatomic, strong) NSMutableArray *filePathsArray; +@property (nonatomic, strong) NSMutableArray *nodesArray; +@property (nonatomic, strong) NSMutableArray *errorsArray; @property (nonatomic) double totalDuration; @property (nonatomic) double currentProgress; // Duration of all videos processed @property (nonatomic) BOOL cancelExportByUser; diff --git a/iMEGA/Utils/ViewControllers/CustomActionViewController/CustomActionViewController.m b/iMEGA/Utils/ViewControllers/CustomActionViewController/CustomActionViewController.m index 36363d4a40..2a158ad93a 100644 --- a/iMEGA/Utils/ViewControllers/CustomActionViewController/CustomActionViewController.m +++ b/iMEGA/Utils/ViewControllers/CustomActionViewController/CustomActionViewController.m @@ -107,17 +107,20 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView UICollectionReusableView *header = [self.collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"actionHeader" forIndexPath:indexPath]; UILabel *title = [header viewWithTag:1]; - title.text = self.node.name; UILabel *info = [header viewWithTag:2]; - info.text = [Helper sizeAndDateForNode:self.node api:[MEGASdkManager sharedMEGASdk]]; - UIImageView *imageView = [header viewWithTag:100]; + title.text = self.node.name; if (self.node.isFile) { [imageView mnz_setThumbnailByNode:self.node]; + info.text = [Helper sizeAndDateForNode:self.node api:[MEGASdkManager sharedMEGASdk]]; } else if (self.node.isFolder) { [imageView mnz_imageForNode:self.node]; - info.text = [Helper filesAndFoldersInFolderNode:self.node api:[MEGASdkManager sharedMEGASdk]]; + if (self.displayMode == DisplayModeFolderLink) { + info.text = [Helper filesAndFoldersInFolderNode:self.node api:[MEGASdkManager sharedMEGASdkFolder]]; + } else { + info.text = [Helper filesAndFoldersInFolderNode:self.node api:[MEGASdkManager sharedMEGASdk]]; + } } return header; diff --git a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h index 47a1165d63..40f9b11b63 100644 --- a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h +++ b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.h @@ -5,6 +5,7 @@ @property (nonatomic, strong) void (^completion)(void); @property (nonatomic, strong) void (^onDismiss)(void); +@property (nonatomic, strong) void (^onBonus)(void); @property (nonatomic) UIImage *image; @property (getter=shouldRoundImage) BOOL roundImage; diff --git a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m index 5b7b7fc8d2..607f1529a5 100644 --- a/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m +++ b/iMEGA/Utils/ViewControllers/CustomModalAlertViewController.m @@ -113,12 +113,16 @@ - (IBAction)dismissTouchUpInside:(UIButton *)sender { - (IBAction)bonusTouchUpInside:(UIButton *)sender { [self fadeOutBackgroundCompletion:^ { - [self dismissViewControllerAnimated:YES completion:^{ - AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; - achievementsVC.enableCloseBarButton = YES; - UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:achievementsVC]; - [UIApplication.mnz_presentingViewController presentViewController:navigation animated:YES completion:nil]; - }]; + if (self.onBonus) { + self.onBonus(); + } else { + [self dismissViewControllerAnimated:YES completion:^{ + AchievementsViewController *achievementsVC = [[UIStoryboard storyboardWithName:@"MyAccount" bundle:nil] instantiateViewControllerWithIdentifier:@"AchievementsViewControllerID"]; + achievementsVC.enableCloseBarButton = YES; + UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:achievementsVC]; + [UIApplication.mnz_presentingViewController presentViewController:navigation animated:YES completion:nil]; + }]; + } }]; } diff --git a/iMEGA/Vendor/SAMKeychain b/iMEGA/Vendor/SAMKeychain index 47e96e5670..d3d64f8c5f 160000 --- a/iMEGA/Vendor/SAMKeychain +++ b/iMEGA/Vendor/SAMKeychain @@ -1 +1 @@ -Subproject commit 47e96e5670a2b2008fb964e5bb81a04ab79ba443 +Subproject commit d3d64f8c5fa14bf2adb2d51a18ee8b6639232ca5 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 87f800820e..879e5d6862 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 87f800820e6b5c95ccb2f26002f0f387438f5a4c +Subproject commit 879e5d686298d3fa20516e92a61c1b0987046676 From 2e0315411f3c40e9e6ead528c1a0daa64443b026 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 09:47:36 +0100 Subject: [PATCH 39/58] Fixed plural issue in user alerts (#11136) --- iMEGA/Languages/Base.lproj/Localizable.strings | 2 ++ .../Notifications/NotificationsTableViewController.m | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index 6f0ef690ba..aedd5a7681 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Removed item from shared folder"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Removed [X] items from a share"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.m b/iMEGA/My Account/Notifications/NotificationsTableViewController.m index 0462ec7854..10202a41bd 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController.m +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.m @@ -270,7 +270,11 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypeRemovedSharesNodes: { int64_t itemCount = [userAlert numberAtIndex:0]; - contentLabel.text = [AMLocalizedString(@"Removed [X] items from a share", @"Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items.") stringByReplacingOccurrencesOfString:@"[X]" withString:[NSString stringWithFormat:@"%lld", itemCount]]; + if (itemCount == 1) { + contentLabel.text = AMLocalizedString(@"Removed item from shared folder", @"Notification when on client side when owner of a shared folder removes folder/file from it."); + } else { + contentLabel.text = [AMLocalizedString(@"Removed [X] items from a share", @"Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items.") stringByReplacingOccurrencesOfString:@"[X]" withString:[NSString stringWithFormat:@"%lld", itemCount]]; + } break; } From 8b6701e419ccb0f3acdd0167cc68bcc5e16519a7 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 10:04:29 +0100 Subject: [PATCH 40/58] Updated localizable strings --- iMEGA/Languages/ar.lproj/Localizable.strings | 24 ++++--- iMEGA/Languages/de.lproj/Localizable.strings | 6 ++ iMEGA/Languages/en.lproj/Localizable.strings | 6 ++ iMEGA/Languages/es.lproj/Localizable.strings | 6 ++ iMEGA/Languages/fr.lproj/Localizable.strings | 20 ++++-- iMEGA/Languages/he.lproj/Localizable.strings | 6 ++ iMEGA/Languages/id.lproj/Localizable.strings | 6 ++ iMEGA/Languages/it.lproj/Localizable.strings | 6 ++ iMEGA/Languages/ja.lproj/Localizable.strings | 6 ++ iMEGA/Languages/ko.lproj/Localizable.strings | 6 ++ iMEGA/Languages/nl.lproj/Localizable.strings | 6 ++ iMEGA/Languages/pl.lproj/Localizable.strings | 6 ++ .../Languages/pt-br.lproj/Localizable.strings | 6 ++ iMEGA/Languages/ro.lproj/Localizable.strings | 6 ++ iMEGA/Languages/ru.lproj/Localizable.strings | 6 ++ iMEGA/Languages/th.lproj/Localizable.strings | 6 ++ iMEGA/Languages/tl.lproj/Localizable.strings | 6 ++ iMEGA/Languages/tr.lproj/Localizable.strings | 6 ++ iMEGA/Languages/uk.lproj/Localizable.strings | 6 ++ iMEGA/Languages/vi.lproj/Localizable.strings | 34 ++++++---- .../zh-Hans.lproj/Localizable.strings | 68 ++++++++++--------- .../zh-Hant.lproj/Localizable.strings | 6 ++ 22 files changed, 194 insertions(+), 60 deletions(-) diff --git a/iMEGA/Languages/ar.lproj/Localizable.strings b/iMEGA/Languages/ar.lproj/Localizable.strings index 313333b53b..fcb0c8afdd 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.strings +++ b/iMEGA/Languages/ar.lproj/Localizable.strings @@ -713,7 +713,7 @@ /* "Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section" */ "onMEGA"="على MEGA"; /* Button text on the Rubbish Bin page. This button will empty all files and folders currently stored in the rubbish bin. */ -"emptyRubbishBin"="Clear Rubbish Bin"; +"emptyRubbishBin"="افراغ سلة المحذوفات"; /* Alert title shown when you tap 'Empty Rubbish Bin' */ "emptyRubbishBinAlertTitle"="كل العناصر في سلة المحذوفات ستحذف نهائيا"; /* Text next to a switch that allows disabling the HTTP protocol for transfers */ @@ -1565,17 +1565,17 @@ /* A title for the Two-Factor Authentication section on the My Account - Security page. */ "twoFactorAuthentication"="المصادقة المزدوجة Two-Factor"; /* Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication */ -"beginSetup"="Begin Setup"; +"beginSetup"="ابدأ الاعداد"; /* A message on the Verify Login page telling the user to enter their 2FA code. */ "pleaseEnterTheSixDigitCode"="يرجى إدخال الرمز المكون من 6 أرقام المولد بتطبيق المصادقة الخاص بك."; /* Title of the dialog displayed to start setup the Two-Factor Authentication */ -"whyYouDoNeedTwoFactorAuthentication"="Why do you need Two-Factor Authentication?"; +"whyYouDoNeedTwoFactorAuthentication"="ما هي فائدة المصادقة المزدوجة 2FA؟"; /* Description for the ‘Two-Factor Authentication’ in the security settings section. */ -"whatIsTwoFactorAuthentication"="Two-Factor Authentication is a second layer of security for your account."; +"whatIsTwoFactorAuthentication"="المصداقة المزدوجة 2FA هي طبقة إضافية من الحماية لحسابك."; /* Description text of the dialog displayed to start setup the Two-Factor Authentication */ -"whyYouDoNeedTwoFactorAuthenticationDescription"="Two-Factor Authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the six digit code only you have access to."; +"whyYouDoNeedTwoFactorAuthenticationDescription"="المصادقة المزدوجة 2FA هي طبقة إضافية من الأمان على حسابك. تحمي حسابك من الولوج حتى وان كان الشخص الذي يحاول الدخول الى حسابك يعرف كلمة المرور خاصتك، كون الدخول الى الحساب يتطلب أيضا الحصول على الرمز المكون من 6 أرقام الذي انت فقط تستطيع الحصول عليه."; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ -"youNeedATwoFactorAuthenticationApp"="You need a two factor authentication app on this device"; +"youNeedATwoFactorAuthenticationApp"="تحتاج الى تطبيق مصادقة مثبت على جهازك الحالي"; /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device and tap on the question mark */ "You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."="You need an authenticator app to enable 2FA on MEGA. You can download and install the Google Authenticator, Duo Mobile, Authy or Microsoft Authenticator app for your phone or tablet."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ @@ -1641,13 +1641,13 @@ /* A section header which contains the file management settings. These settings allow users to remove duplicate files etc. */ "File Management"="إدارة الملف"; /* Title of the option to enable or disable file versioning on Settings section */ -"File versioning"="File versioning"; +"File versioning"="النسخ المعدلة للملفات"; /* Subtitle of the option to enable or disable file versioning on Settings section */ "Enable or disable file versioning for your entire account.[Br]You may still receive file versions from shared folders if your contacts have this enabled."="Enable or disable file versioning for your entire account.\nYou may still receive file versions from shared folders if your contacts have this enabled."; /* A confirmation message when the user chooses to disable file versioning. */ "When file versioning is disabled, the current version will be replaced with the new version once a file is updated (and your changes to the file will no longer be recorded). Are you sure you want to disable file versioning?"="عند تعطيل نسخ الملف، سيتم استبدال الإصدار الحالي بالإصدار الجديد بمجرد تحديث الملف (ولن يتم تسجيل التغييرات التي أجريتها على الملف). هل تريد بالتأكيد تعطيل نسخ الملفات؟"; /* Settings preference title to show file versions info of the account */ -"File versions"="File versions"; +"File versions"="النسخ المعدلة للملف"; /* A title message in the user’s account settings for showing the storage used for file versions. */ "Total size taken up by file versions:"="الحجم الكامل لجميع نسخ الملف:"; /* The title of the section about deleting file versions in the settings. */ @@ -1657,7 +1657,7 @@ /* A warning note about deleting all file versions in the settings section. */ "All current files will remain. Only historic versions of your files will be deleted."="كل الملفات الحالية ستبقى دون تعديل، فقد النسخ القديمة للملفات سوف تحذف"; /* Text of the dialog to delete all the file versions of the account */ -"You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.\n\nPlease note that the current files will not be deleted."; +"You are about to delete the version histories of all files. Any file version shared to you from a contact will need to be deleted by them.[Br][Br]Please note that the current files will not be deleted."="انت بصدد حذف كل النسخ المعدلة لكل الملفات. أي نسخة معدلة لملف مشارك معك من جهة اتصال يجب ان يحذف من قبله. \n\nيرجى الملاحظة انه لن يتم حذف النسخ الحالية للملفات."; /* Title for the Rubbish-Bin Cleaning Scheduler feature */ "Rubbish-Bin Cleaning Scheduler:"="جدولة تنظيف سلة المهملات:"; /* A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. */ @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="عنصر محذوف من مجلد المشاركة"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="تم إزالة [X] عناصر من المشاركة"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="لقد شاركت بشكل عام %1 (%2)"; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="تم إعادة الخدمة ل %1 (%2)"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="لا يمكن إكمال هذا الإجراء لأنه سيتجاوز حد التخزين الحالي"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="لا يمكن متابعة الترفيع بسبب امتلاء حسابك"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/de.lproj/Localizable.strings b/iMEGA/Languages/de.lproj/Localizable.strings index 069635edd6..b10f7ab062 100644 --- a/iMEGA/Languages/de.lproj/Localizable.strings +++ b/iMEGA/Languages/de.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Datei oder Ordner aus Freigabe entfernt"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="[X] Dateien und Ordner aus Freigabe entfernt"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Ihr per exportiertem Link zugänglich gemachter %1 (%2) wurde auf eine Beschwerde hin gelöscht."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Die Löschung von %1 (%2) wurde rückgängig gemacht."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Ihr verfügbarer Speicherplatz reicht nicht aus"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Sie haben Ihr Speicherplatzlimit erreicht. Ihr Upload wurde daher angehalten."; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/en.lproj/Localizable.strings b/iMEGA/Languages/en.lproj/Localizable.strings index e445ebf2bf..aedd5a7681 100644 --- a/iMEGA/Languages/en.lproj/Localizable.strings +++ b/iMEGA/Languages/en.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Removed item from shared folder"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Removed [X] items from a share"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Your publicly shared %1 (%2) has been taken down."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Your taken down %1 (%2) has been reinstated."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="This action can not be completed as it would take you over your current storage limit"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/es.lproj/Localizable.strings b/iMEGA/Languages/es.lproj/Localizable.strings index 2e0ec72648..188f738c81 100644 --- a/iMEGA/Languages/es.lproj/Localizable.strings +++ b/iMEGA/Languages/es.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Ha añadido 1 archivo y %lld carpetas"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Ha añadido [A] archivos y [B] carpetas"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Elemento eliminado de una carpeta compartida"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Se han eliminado [X] elementos compartidos"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Tu %1 (%2) que has compartido públicamente ha sido eliminado/a."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="La eliminación de: %1 (%2) ha sido revocada."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Esta acción no se puede completar ya que te llevaría por encima de tu límite de almacenamiento actual"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="La subida no puede continuar porque tu cuenta está llena"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Permitir que tus contactos vean tu último acceso a MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/fr.lproj/Localizable.strings b/iMEGA/Languages/fr.lproj/Localizable.strings index 967af8837f..0d3efda1ff 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.strings +++ b/iMEGA/Languages/fr.lproj/Localizable.strings @@ -191,7 +191,7 @@ /* */ "folderLinkUnavailableText4"="Le lien du dossier a été désactivé par l’utilisateur."; /* Text shown on the app when you don't have connection to the internet or when you have lost it */ -"noInternetConnection"="Aucune connexion Internet"; +"noInternetConnection"="Il n’y a pas de connexion à Internet"; /* Title shown when a folder doesn't have any files */ "emptyFolder"="Le dossier est vide"; /* Title shown when your Cloud Drive is empty, when you don't have any files. */ @@ -1364,8 +1364,10 @@ /* Between paragraph 5 and 6 we have to put the string with the 'autorenewableDescription' key */ /* Paragraph 6 of the general description of MEGA shown on the apps stores in Android, iOS and Windows Phone. Please keep the multiple line format. */ "generalDescriptionParagraph6"="Pour plus d’informations, veuillez consulter notre site Web : https://mega.nz/\nConditions générales d’utilisation : https://mega.nz/terms\nPolitique de confidentialité : https://mega.nz/privacy"; -/* Label shown when you receive an incoming call, before start the call. */ +/* Label shown when you call someone (outgoing call), before the call starts. */ "calling..."="Appel…"; +/* notification subtitle of incoming calls */ +"Incoming call"="Appel entrant"; /* Notification text body shown when you have missed one audio call */ "missedAudioCall"="Appel vocal manqué"; /* Notification text body shown when you have missed several audio calls. [A] = {number of missed audio calls} */ @@ -1401,7 +1403,7 @@ /* Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible. */ "autoAccept"="Accepter automatiquement"; /* Footer that explains the way Auto-Accept works for QR codes */ -"autoAcceptFooter"="Les contacts qui lisent votre code QR seront automatiquement ajoutés à votre liste de contacts."; +"autoAcceptFooter"="Les contacts qui balayeront votre code QR seront automatiquement ajoutés à votre liste de contacts."; /* Action to reset the current valid QR code of the user */ "resetQrCode"="Réinitialiser le code QR"; /* Footer that explains what would happen if the user resets his/her QR code */ @@ -1409,13 +1411,13 @@ /* Title for view that displays the QR code of the user. String as short as possible. */ "myCode"="Mon code"; /* Segmented control title for view that allows the user to scan QR codes. String as short as possible. */ -"scanCode"="Lire un code"; +"scanCode"="Balayer un code"; /* Label that encourage the user to line the QR to scan with the camera */ -"lineCodeWithCamera"="Aligner le code QR pour le lire avec l’appareil photo de votre appareil"; +"lineCodeWithCamera"="Aligner le code QR pour le balayer avec l’appareil photo de votre appareil"; /* Error text shown when the user scans a QR that is not valid. String as short as possible. */ "invalidCode"="Code invalide"; /* Success text shown in a label when the user scans a valid QR. String as short as possible. */ -"codeScanned"="Code lu"; +"codeScanned"="Code balayé"; /* title of the screen that shows the users with whom the user has shared a folder */ "sharedWidth"="Partagé avec :"; /* */ @@ -1752,6 +1754,8 @@ "Added 1 file and %lld folders"="1 fichier et %lld dossiers ont été ajoutés"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[A] fichiers [B] dossiers ont été ajoutés"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Un élément a été retiré d’un dossier partagé"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="[X] éléments ont été retirés d’un partage"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1766,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Votre partage public %1 (%2) a été retiré."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Votre %1 (%2) retiré a été rétabli"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Cette action ne peut pas être menée à bien, car elle vous ferait dépasser votre limite de stockage actuelle"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Vos téléversements ne peuvent pas se poursuivre, car votre espace de stockage est plein"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Permettre à vos contacts de voir l’heure de votre dernière activité sur MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/he.lproj/Localizable.strings b/iMEGA/Languages/he.lproj/Localizable.strings index b39ee26483..f1687c4c1d 100644 --- a/iMEGA/Languages/he.lproj/Localizable.strings +++ b/iMEGA/Languages/he.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="מחק פריט אחד מהשיתוף"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="מחק [X] פריטים מהשיתוף"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="ה%1 (%2) משותף שהורד, הועלה מחדש."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="ה%1 (%2) שהורד, הועלה מחדש."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="פעולה זו לא אפשרית מכיוון והיא תגרום להעברת מכסת השטח שלך"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/id.lproj/Localizable.strings b/iMEGA/Languages/id.lproj/Localizable.strings index 73ec1ee2ee..ee3c9a2592 100644 --- a/iMEGA/Languages/id.lproj/Localizable.strings +++ b/iMEGA/Languages/id.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Menghapus item dari folder berbagi"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Menghapus [X] item dari berbagi"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Anda punya %1 (%2) telah dicopot."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Pencopotan %1 (%2) telah dikembalikan."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Tindakan ini tidak dapat diselesaikan karena akan membawa Anda melewati batas penyimpanan Anda saat ini"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Unggahan Anda tidak dapat dilanjutkan karena akun Anda penuh"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/it.lproj/Localizable.strings b/iMEGA/Languages/it.lproj/Localizable.strings index 77504e623b..751912743d 100644 --- a/iMEGA/Languages/it.lproj/Localizable.strings +++ b/iMEGA/Languages/it.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="1 file e %lld cartelle aggiunte"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[A] file e [B] cartelle aggiunte"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Oggetto rimosso dalla cartella condivisa"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Rimossi [X] oggetti dalla condivisione"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Il/la tuo/a %1 condiviso/a pubblicamente (%2) è stato/a chiuso/a."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Il/la tuo/a %1 (%2) chiuso/a è stato/a riattivato/a."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Questa azione non può essere completata in quanto ti farebbe superare il limite del tuo spazio di archiviazione"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Il tuo o i tuoi upload non possono procedere poiché il tuo account è pieno."; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Permetti ai tuoi contatti di vedere l'ultima volta in cui sei stato attivo su MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/ja.lproj/Localizable.strings b/iMEGA/Languages/ja.lproj/Localizable.strings index 03ada69499..78d57873c1 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.strings +++ b/iMEGA/Languages/ja.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="1個のファイルと%lld個のフォルダが追加されました"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[A]個のファイルと[B]個のフォルダが追加されました"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="共有フォルダから削除された項目"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="共有から削除された[X]項目"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="あなたの公開共有された%1(%2)は削除されました。"; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="あなたの削除された%1(%2)は復活しました。"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="この操作は、現在のストレージ容量上限を超えてしまうため、完了できません。"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="あなたのアカウントがいっぱいのため、あなたのアップロード(複数)は続行できません"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="あなたの連絡先がMEGAであなたが最後に活動していた時間を見れるようにします。"; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/ko.lproj/Localizable.strings b/iMEGA/Languages/ko.lproj/Localizable.strings index bd8a9c0c8a..2385796a5e 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.strings +++ b/iMEGA/Languages/ko.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="파일 1개와 폴더 %lld개 추가됨"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="파일 [A]개와 폴더 [B]개 추가됨"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="공유 폴더에서 항목을 제거했습니다"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="[X] 항목이 공유에서 제거됨"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="공개적으로 공유된 %1 (%2)가 게시 중단되었습니다."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="게시 중단 %1 (%2)가 복원되었습니다."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="이 행동은 현재 저장 공간 한도를 초과할 수 있어서 완료될 수 없습니다"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="당신의 계정이 꽉 차서 업로드를 진행할 수 없습니다."; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="당신의 연락처가 당신이 최근에 MEGA에서 활동한 시간을 볼 수 있도록 합니다."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/nl.lproj/Localizable.strings b/iMEGA/Languages/nl.lproj/Localizable.strings index 4d46d534af..d1225ecbeb 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.strings +++ b/iMEGA/Languages/nl.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="1 bestand en %lld mappen toegevoegd"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[A] bestanden en [B] mappen toegevoegd"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Verwijderd item van gedeelde map"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Verwijder [X] items van gedeelde map"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Uw publiekelijk gedeelde %1 (%2) is weggehaald."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Uw weggehaalde %1 (%2) is hersteld."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Deze actie kan niet worden voltooid omdat het u over de huidige opslaglimiet brengt."; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Uw upload(s) kunnen niet doorgaan omdat uw account vol is"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Contacten toestaan om te zien wanneer u de laatste keer actief was op MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/pl.lproj/Localizable.strings b/iMEGA/Languages/pl.lproj/Localizable.strings index 098f84d82f..63c063cabe 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.strings +++ b/iMEGA/Languages/pl.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Dodano 1 plik i %lld katalogów"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Dodano [A] plików i [B] katalogów"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Usuń element z udostępnionego katalogu"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Usunięto [X] elementów z udostępnionego katalogu"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Twój udostępniony publicznie katalog %1 (%2) został zablokowany."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Zablokowane pliki %1 (%2) zostały przywrócone."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Ta operacja nie zostanie zakończona prawidłowo, ponieważ przekroczy dostępny limit pamięci na Twoim koncie."; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Twoje przesłanie nie może być kontynuowane, ponieważ Twoje konto jest pełne"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Pozwól swoim kontaktom zobaczyć ostatni raz, kiedy byłeś aktywny w MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/pt-br.lproj/Localizable.strings b/iMEGA/Languages/pt-br.lproj/Localizable.strings index 6da6ab0fda..63fca39729 100644 --- a/iMEGA/Languages/pt-br.lproj/Localizable.strings +++ b/iMEGA/Languages/pt-br.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="%lld pastas e 1 arquivo adicionados"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[B] pastas e [A] arquivos adicionados"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Item eliminado da pasta compartilhada"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="[X] itens foram eliminados da pasta compartilhada"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="O(a) %1 (%2) compartilhado publicamente foi removido(a)."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="A remoção de %1 (%2) foi revogada."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Esta ação não pode ser concluída, pois ultrapassaria o seu limite de armazenamento atual."; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Não foi possível fazer o upload porque a sua conta está cheia"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Permitir que seus contatos vejam o seu último acesso ao MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/ro.lproj/Localizable.strings b/iMEGA/Languages/ro.lproj/Localizable.strings index fdd0ec0124..1194860802 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.strings +++ b/iMEGA/Languages/ro.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Element eliminat din folderul partajat"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="[X] elemente eliminate dintr-o partajare"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Partajarea ta publică %1 (%2) a fost eliminată."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Eliminarea ta %1 (%2) a fost restabilită."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Această acţiune nu poate fi finalizată, deoarece îţi va depăşi limita actuală de stocare"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/ru.lproj/Localizable.strings b/iMEGA/Languages/ru.lproj/Localizable.strings index f06fe1fdac..2e84bac6ec 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.strings +++ b/iMEGA/Languages/ru.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Добавлен 1 файл и папки(%lld)"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Добавлены файлы([A]) и папки([B])"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Отозван элемент из общей папки"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Закрыт доступ к [X] элементам"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Ваш элемент с общим доступом (%1 (%2)) убран."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Убранный элемент восстановлен: %1 (%2)."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Это действие нельзя завершить, иначе будет превышен текущий лимит хранения"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Загрузки нельзя продолжить, потому что диск заполнен"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Разрешать контактам видеть, когда я был активен в MEGA последний раз."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/th.lproj/Localizable.strings b/iMEGA/Languages/th.lproj/Localizable.strings index 198b259428..56b0c0cd6e 100644 --- a/iMEGA/Languages/th.lproj/Localizable.strings +++ b/iMEGA/Languages/th.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="เพิ่ม 1 ไฟล์และ %lld โฟลเดอร์แล้ว"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="เพิ่ม [A] ไฟล์และ [B] โฟลเดอร์แล้ว"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="ลบรายการออกจากโฟลเดอร์ที่แชร์"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="ได้เอา [X] รายการออกจากการแชร์"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="การแชร์สาธารณะของคุณ %1 (%2) ถูกแจ้งว่าให้ลบเนื้อหา"; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="การแจ้งให้ดำเนินการของคุณ %1 (%2) ถูกคืนสิทธิ์เรียบร้อยแล้ว"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="ไม่สามารถดำเนินการนี้ได้ เพราะจะทำให้พื้นที่เก็บข้อมูลปัจจุบันของคุณเกินขีดจำกัดได้"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="การอัปโหลดของคุณไม่สามารถดำเนินการต่อได้ เนื่องจากบัญชีของคุณเต็มแล้ว"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="อนุญาตให้ผู้ติดต่อของคุณเห็นว่า เป็นครั้งสุดท้ายที่คุณใช้งาน MEGA"; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/tl.lproj/Localizable.strings b/iMEGA/Languages/tl.lproj/Localizable.strings index c2f7432ac4..e91ea28bda 100644 --- a/iMEGA/Languages/tl.lproj/Localizable.strings +++ b/iMEGA/Languages/tl.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Tanggalin ang item sa binabahaging folder"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Natanggal [X] ang items sa binahagi"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Ang binahagi mo sa lahat na %1 (%2) ay tinanggal na."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Ang inyong taken down na %1 (%2) ay muling naibalik."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="This action can not be completed as it would take you over your current storage limit"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/tr.lproj/Localizable.strings b/iMEGA/Languages/tr.lproj/Localizable.strings index b82f55a095..3ff7b4dd7a 100644 --- a/iMEGA/Languages/tr.lproj/Localizable.strings +++ b/iMEGA/Languages/tr.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="1 dosya ve %lld klasör eklendi"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="[A] dosya ve [B] klasör eklendi"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Öğe paylaşılan klasörden kaldırıldı"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Paylaşılanlardan [X] nesne silindi"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Herkese açık olarak paylaşılan %1 (%2), telif hakkı nedeniyle yayından kaldırıldı."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Telif hakkı nedeniyle yayından kaldırılan %1 (%2), tekrar yayına alındı."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Bu eylem şu andaki depolama sınırını aşacağından dolayı tamamlanamaz."; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Hesabınız dolu olduğu için yükleme(ler) devam edemez"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Kişilerinizin MEGA'daki son etkinlik zamanınızı görmesine izin verin."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/uk.lproj/Localizable.strings b/iMEGA/Languages/uk.lproj/Localizable.strings index bd7ce7ee82..77586a9622 100644 --- a/iMEGA/Languages/uk.lproj/Localizable.strings +++ b/iMEGA/Languages/uk.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Видалений елемент зі спільної теки"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Вилучено [X] елементів зі спільного доступу"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Загальний доступ до %1 (%2) було призупинено."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Раніше призупинене %1 (%2) було відновлено."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="This action can not be completed as it would take you over your current storage limit"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Your upload(s) cannot proceed because your account is full"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/vi.lproj/Localizable.strings b/iMEGA/Languages/vi.lproj/Localizable.strings index 01434accc7..c5481bfd4c 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.strings +++ b/iMEGA/Languages/vi.lproj/Localizable.strings @@ -1685,15 +1685,15 @@ /* Text shown for switching from list view to thumbnail view. */ "Thumbnail view"="Hiện dạng ảnh nhỏ"; /* Message to inform the local user that someone has joined the current group call */ -"%@ joined the call."="%@ joined the call."; +"%@ joined the call."="%@ tham gia vào cuộc gọi."; /* Message to inform the local user that someone has left the current group call */ -"%@ left the call."="%@ left the call."; +"%@ left the call."="%@ đã rời khỏi cuội gọi."; /* Message to inform the local user is having a bad quality network with someone in the current group call */ -"Poor connection."="Poor connection."; +"Poor connection."="Mạng yếu."; /* Message shown in a chat room when there is an active group call */ -"There is an active group call. Tap to join."="There is an active group call. Tap to join."; +"There is an active group call. Tap to join."="Có một cuộc gọi nhóm đang diễn ra. Chạm để tham gia."; /* Message shown in a chat room for a group call in progress displaying the duration of the call */ -"Touch to return to call %@"="Touch to return to call %@"; +"Touch to return to call %@"="Chạm để quay trở lại cuộc gọi %@"; /* Menu item to change from grid view to list view */ "List view"="Xem dạng danh sách"; /* Menu item to change from list view to grid view */ @@ -1709,7 +1709,7 @@ /* The header of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. */ "Takedown reinstated"="Mục bị gỡ bỏ đã được phục hồi"; /* Label shown inside an unseen notification */ -"New"="New"; +"New"="Mới"; /* When a contact sent a contact/friend request */ "Sent you a contact request"="Vừa gửi bạn lời mời thêm vào danh bạ"; /* A notification that the other user cancelled their contact request so it is no longer valid. E.g. user@email.com cancelled their contact request. */ @@ -1739,21 +1739,23 @@ /* This is shown in the Notification dialog when the email address of a contact is not found and access to the share is lost for some reason (e.g. share removal or contact removal). */ "Access to folders was removed."="Quyền truy cập tới thư mục đã bị xóa."; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added 1 file"="Added 1 file"; +"Added 1 file"="Đã thêm 1 tệp"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added %lld files"="Added %lld files"; +"Added %lld files"="Đã thêm %lld tệp"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added 1 folder"="Added 1 folder"; +"Added 1 folder"="Đã thêm 1 thư mục"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added %lld folders"="Added %lld folders"; +"Added %lld folders"="Đã thêm %lld thư mục"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added 1 file and 1 folder"="Added 1 file and 1 folder"; +"Added 1 file and 1 folder"="Đã thêm 1 tệp và 1 thư mục"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added %lld files and 1 folder"="Added %lld files and 1 folder"; +"Added %lld files and 1 folder"="Đã thêm %lld tệp và 1 thư mục"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added 1 file and %lld folders"="Added 1 file and %lld folders"; +"Added 1 file and %lld folders"="Đã thêm 1 tệp và %lld thư mục"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ -"Added [A] files and [B] folders"="Added [A] files and [B] folders"; +"Added [A] files and [B] folders"="Đã thêm [A] tệp và [B] thư mục"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="Đã loại bỏ một mục khỏi việc chia sẻ"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="Đã loại bỏ [X] mục khỏi việc chia sẻ"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="Một %1 của bạn (tên %2) đã bị tháo bỏ do vi phạm bản quyền."; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="Nội dung %1 (tên %2) đã được phục hồi lại."; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="Thao tác này không thể hoàn tất bởi vì toàn bộ không gian lưu trữ sẽ bị dùng hết."; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="Phiên tải lên không thể tiến hành vì tài khoản đã bị đầy"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings index 09a099a0fa..0c85f991a2 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings @@ -115,19 +115,19 @@ /* */ "weak"="弱"; /* */ -"passwordVeryWeakOrWeak"="您的密码太容易被猜到了。请尝试增加密码长度。结合大小写。添加特殊字元。不要使用名字或单词。"; +"passwordVeryWeakOrWeak"="您的密码很容易被猜到。轻尝试增加密码长度。结合大小写字母。添加特殊符号。请不要使用名字或词语。"; /* Label displayed during checking the strength of the password introduced. Represents Medium security */ "medium"="中等"; /* */ -"passwordMedium"="您的密码已足以继续,但仍建议可再加强密码强度。"; +"passwordMedium"="您可使用此密码,但建议您增加密码强度。"; /* */ -"good"="好"; +"good"="很好"; /* */ -"passwordGood"="这个密码将可承受最典型的暴力破解攻击。请确保您会好好记牢。"; +"passwordGood"="该密码不容易被攻破。请确保您会将它牢记。"; /* Label displayed during checking the strength of the password introduced. Represents Strong security */ "strong"="强"; /* */ -"passwordStrong"="这个密码将可承受最典型的暴力破解攻击。请确保您会好好记牢。"; +"passwordStrong"="该密码不容易被攻破。请确保您会将它牢记。"; /* */ "agreeWithTheMEGATermsOfService"="我同意MEGA的服务条款"; /* Error text shown when you have not entered a correct name */ @@ -137,7 +137,7 @@ /* Error text shown when you don't have selected the checkbox to agree with the Terms of Service */ "termsCheckboxUnselected"="您需要同意MEGA服务条款才可注册账号。"; /* Error text shown when the users tries to create an account with an email already in use */ -"emailAlreadyRegistered"="这个电子邮件信箱在Mega已被注册。"; +"emailAlreadyRegistered"="此邮箱已在MEGA注册过"; /* Title shown just after doing some action that requires confirming the action by an email */ "awaitingEmailConfirmation"="正在等待确认电子信箱。"; /* Text shown on the confirm account view to remind the user what to do */ @@ -193,7 +193,7 @@ /* Text shown on the app when you don't have connection to the internet or when you have lost it */ "noInternetConnection"="无网络连接"; /* Title shown when a folder doesn't have any files */ -"emptyFolder"="文件夹中没有文件"; +"emptyFolder"="空的文件夹"; /* Title shown when your Cloud Drive is empty, when you don't have any files. */ "cloudDriveEmptyState_title"="您的云盘中没有文件"; /* Button title shown in empty views when you can 'Add files' */ @@ -261,7 +261,7 @@ /* Text shown when one file has been selected to be downloaded but it's on the queue to be downloaded, it's pending for download */ "queued"="已入进程"; /* A label to indicate a paused state for a transfer item (upload/download). */ -"paused"="暂停"; +"paused"="已暂停"; /* Label for the state of a transfer when is being completing - (String as short as possible). */ "Completing..."="即将完成..."; /* Label for the state of a transfer when is being retrying - (String as short as possible). */ @@ -269,7 +269,7 @@ /* Section title of the 'Sort by' */ "sortTitle"="排序"; /* Button title to 'Save' the selected option */ -"save"="储存"; +"save"="保存"; /* Sort by option (1/6). This one orders the files alphabethically*/ "nameAscending"="名字(升序)"; /* Sort by option (2/6). This one arranges the files on reverse alphabethical order */ @@ -329,7 +329,7 @@ /* Title shown under the action that allows you to save several images to your camera roll */ "saveImages"="保存照片"; /* Title shown under the action that allows you to get a link to file or folder */ -"getLink"="取得链接"; +"getLink"="获得链接"; /* Title shown under the action that allows you to get several links to files and/or folders */ "getLinks"="创建共享链接"; /* Item menu option upon right click on one or multiple files. */ @@ -509,11 +509,11 @@ /* Text shown to explain what means 'Enable Camera Uploads'. The 'Cloud Drive' is the MEGA section, so please keep consistency */ "automaticallyBackupYourPhotos"="自动将相片备份到云盘"; /* Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA */ -"enableCameraUploadsButton"="允许从相机上传"; +"enableCameraUploadsButton"="启用相机上传"; /* Success message shown when Camera Uploads has been enabled */ "cameraUploadsEnabled"="已启用相机上传"; /* Message shown when the camera uploads have been completed */ -"cameraUploadsComplete"="从相机上传成功"; +"cameraUploadsComplete"="相机上传完成"; /* Message shown while uploading files. Singular. */ "cameraUploadsPendingFile"="正在上传,1个文件待处理"; /* Message shown while uploading files. Plural. */ @@ -637,7 +637,7 @@ /* Permissions given to the user you share your folder with */ "readAndWrite"="读取与写入"; /* Permissions given to the user you share your folder with */ -"fullAccess"="全权使用"; +"fullAccess"="完全访问权限"; /* Title of the Settings section */ "settingsTitle"="设定"; /* Title of one of the Settings sections where you can set up the 'Camera Uploads' options */ @@ -653,7 +653,7 @@ /* Title of one of the Settings sections where you can set up the 'Language' of the app */ "language"="语言"; /* Title of one of the Settings sections where you can 'Send Feedback' to MEGA */ -"sendFeedbackLabel"="送出建议"; +"sendFeedbackLabel"="发送反馈"; /* Message body of the email that appears when the users tap on "Send feedback" */ "pleaseWriteYourFeedback"="请于此处写下您的意见"; /* Text shown when you want to send feedback of the app and you don't have an email account set up on your device */ @@ -785,9 +785,9 @@ /* An option to reset the password. */ "forgotPassword"="忘记密码了?"; /* Message shown during forgot your password process if the link to reset password has expired */ -"recoveryLinkHasExpired"="这个恢复链接已失效,请再试一次。"; +"recoveryLinkHasExpired"="这个恢复链接已过期,请再试一次。"; /* Headline of the password reset recovery procedure */ -"passwordReset"="密码重设"; +"passwordReset"="密码重置"; /* A message shown to explain that the user has to input (type or paste) their recovery key to continue with the reset password process. */ "pleaseEnterYourRecoveryKey"="请输入您的还原金钥"; /* Label for any 'Recovery Key' button, link, text, title, etc. Preserve uppercase - (String as short as possible). The Recovery Key is the new name for the account 'Master Key', and can unlock (recover) the account if the user forgets their password. */ @@ -799,10 +799,10 @@ /* */ "yourPasswordHasBeenReset"="密码已重置。请立即登入账户。"; /* Label for any 'Send' button, link, text, title, etc. - (String as short as possible). */ -"send"="送出"; +"send"="发送"; /* The title of the screen to park an account. */ /* Headline for parking an account (basically restarting from scratch) */ -"parkAccount"="封存账号"; +"parkAccount"="封存账户"; /* Label text of a checkbox to ensure that the user is aware that the data of his current account will be lost when proceeding unless they remember their password or have their master encryption key (now renamed "Recovery Key") */ "startingFreshAccount"="我已了解我正在重新注册一个全新且空白的账户。除非我能想起原本的密码,或者找到之前汇出之还原金钥,否则我将失去我现在账户中的所有资料。"; /* Caption of the button to proceed */ @@ -842,7 +842,7 @@ /* Footer text that explain what will happen if "Erase local data" is enabled */ "failedAttempstSectionTitle"="于10次密码输入错误后,自动登出并删除本机资料"; /* Menu item */ -"help"="客户服务"; +"help"="帮助"; /* Title of the section to access MEGA's help centre */ "helpCentreLabel"="帮助中心"; /* Section title that links you to the webpage that let you join and test the beta versions */ @@ -876,7 +876,7 @@ /* Button title that shows the different MEGA PRO plans to which you can upgrade. (String as short as possible) */ "buyPRO"="购买 PRO"; /* Caption of a button to upgrade the account to Pro status */ -"upgrade"="付费升级"; +"upgrade"="升级"; /* Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile */ "viewAndEditProfile"="查看并修改帐户资料"; /* Label for any 'Profile' button, link, text, title, etc. - (String as short as possible). */ @@ -934,7 +934,7 @@ /* */ "days"="天"; /* Label to show that an error related with expiration occurs during a SDK operation. */ -"expired"="过期"; +"expired"="已过期"; /* Title of the button which logs out from your account. */ "logoutLabel"="注销"; /* String shown when you are logging out of your account. */ @@ -958,7 +958,7 @@ /* Title for "Used" MEGA space. */ "usedSpaceLabel"="已用"; /* Title for MEGA "Available" space. */ -"availableLabel"="可使用的"; +"availableLabel"="可用"; /* title of the My Account screen */ "accountType"="账号类型:"; /* Text relative to the MEGA account level. UPPER CASE */ @@ -990,7 +990,7 @@ /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ "transferQuota"="流量限制"; /* Label for any 'Limited' button, link, text, title, etc. - (String as short as possible). */ -"limited"="有限制的"; +"limited"="限制"; /* A message shown at registration time when users have to select their plan. The FREE 50 GB plan is subject to the achievements program. */ "subjectToYourParticipationInOurAchievementsProgram"="取决于您所达成的奖励挑战。"; /* Price asociated with the MEGA PRO account level you can subscribe */ @@ -1126,9 +1126,9 @@ /* */ "busy"="忙碌"; /* title of the contact properties screen */ -"contactInfo"="联系资讯"; +"contactInfo"="联系人信息"; /* */ -"notifications"="通知信息"; +"notifications"="通知"; /* Title to perform the action of sending a message to a contact. */ "sendMessage"="发送信息"; /* Title for a section on the fingerprint warning dialog. Below it is a button which will allow the user to verify their contact's fingerprint credentials. */ @@ -1174,7 +1174,7 @@ /* A button label. The button allows the user to mute a conversation. */ "mute"="静音"; /* A button label. The button allows the user to get more info of the current context. */ -"info"="资讯"; +"info"="信息"; /* A button label. The button allows the user to close the conversation. */ "close"="关闭"; /* Alert text that explains what means confirming the action 'Leave' */ @@ -1313,7 +1313,7 @@ /* Label to show that an error of Out of range occurs during a SDK operation. */ "Out of range"="超出范围"; /* Label to show that an error related with expiration occurs during a SDK operation. */ -"Expired"="过期"; +"Expired"="已过期"; /* Label to show that an error related with a resource Not found occurs during a SDK operation. */ "Not found"="未找到"; /* Label to show that an error related with a circular linkage occurs during a SDK operation. */ @@ -1435,9 +1435,9 @@ /* Title of the incoming shared folders of a user in singular */ "sharedFolder"="已分享的文件夹"; /* The label of the folder creation time. For example: "Created: 2013-02-02" (no need to put the colon punctuation in the translation) */ -"created"="建立"; +"created"="创建于"; /* Label for what a selection contains. For example: "Contains: 3 folders & 13 files". (no need to put the colon punctuation in the translation) */ -"contains"="包含"; +"contains"="包括"; /* A label for any 'Modified' text or title. For example to show the modification date of a file/folder. */ "modified"="最后修改"; /* used for example when a folder is shared with 2 or more users */ @@ -1447,9 +1447,9 @@ /* Message to display the number of historical versions of files. Please keep [X] as it will be replaced at the runtime. */ "xVersions"="[X]个版本"; /* Label of the option menu. When clicking this button, the app shows the info of the file */ -"fileInfo"="文件资讯"; +"fileInfo"="文件信息"; /* Label of the option menu. When clicking this button, the app shows the info of the folder */ -"folderInfo"="文件夹资讯"; +"folderInfo"="文件夹信息"; /* Question to ensure user wants to delete file version. */ "deleteVersion"="是否删除这个版本?"; /* Message to notify user the file version will be permanently removed */ @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="已添加1个文件和%lld文件夹"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="已添加[A]文件和[B]文件夹"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="已删除共享项目"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="已删除[X]个共享项目"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="您公开分享的%1 (%2) 已被移除。"; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="您%1 (%2) 的删除已恢复。"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="操作无法完成,因为您的储存量将超过限额。"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="由于您的账户已满,您的上传无法继续"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="允许您的联系人查看您上次在MEGA的活跃时间。"; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings index de20d7746d..245805d26b 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings @@ -1754,6 +1754,8 @@ "Added 1 file and %lld folders"="Added 1 file and %lld folders"; /* Content of a notification that informs how many files and folders have been added to a shared folder */ "Added [A] files and [B] folders"="Added [A] files and [B] folders"; +/* Notification when on client side when owner of a shared folder removes folder/file from it. */ +"Removed item from shared folder"="已從分享資料夾中移除檔案"; /* Notification popup. Notification for multiple removed items from a share. Please keep [X] as it will be replaced at runtime with the number of removed items. */ "Removed [X] items from a share"="已從共享中移除[X]項目"; /* A notification telling the user that their Pro plan payment was successfully received. The %1 indicates the name of the Pro plan they paid for e.g. Lite, PRO III. */ @@ -1768,6 +1770,10 @@ "Your publicly shared %1 (%2) has been taken down."="您公開分享的 %1 (%2) 已被移除。"; /* The text of a notification indicating that a file or folder that was taken down has now been restored due to a successful counter-notice. The %1 placeholder will be replaced with the text ‘file’ or ‘folder’. The %2 will be replaced with the name of the file or folder. */ "Your taken down %1 (%2) has been reinstated."="您 %1 (%2) 之刪除已復原。"; +/* Error message shown to user when a copy/import operation would take them over their storage limit. */ +"This action can not be completed as it would take you over your current storage limit"="因將超出目前的儲存空間限制,此操作無法完成"; +/* uploads over storage quota warning dialog title */ +"Your upload(s) cannot proceed because your account is full"="由於您的帳戶已滿,您的上傳無法繼續"; /* Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. */ "Allow your contacts to see the last time you were active on MEGA."="Allow your contacts to see the last time you were active on MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ From 3c7883b0eb6c9d9486c52ba5d965d03c79eb39e1 Mon Sep 17 00:00:00 2001 From: Javier Santana Espejo Date: Wed, 9 Jan 2019 11:06:48 +0100 Subject: [PATCH 41/58] Added check to avoid showing 'Back' next to the navigation bar button item --- iMEGA/Contacts/ContactDetailsViewController.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/iMEGA/Contacts/ContactDetailsViewController.m b/iMEGA/Contacts/ContactDetailsViewController.m index 353d33af1c..d32a23a393 100644 --- a/iMEGA/Contacts/ContactDetailsViewController.m +++ b/iMEGA/Contacts/ContactDetailsViewController.m @@ -51,10 +51,6 @@ @implementation ContactDetailsViewController - (void)viewDidLoad { [super viewDidLoad]; - self.backBarButtonItem.image = self.backBarButtonItem.image.imageFlippedForRightToLeftLayoutDirection; - if (self.contactDetailsMode == ContactDetailsModeFromChat) { - self.navigationItem.leftBarButtonItem = self.backBarButtonItem; - } self.navigationItem.title = AMLocalizedString(@"contactInfo", @"title of the contact properties screen"); self.user = [[MEGASdkManager sharedMEGASdk] contactForEmail:self.userEmail]; @@ -69,6 +65,11 @@ - (void)viewDidLoad { } } + self.backBarButtonItem.image = self.backBarButtonItem.image.imageFlippedForRightToLeftLayoutDirection; + if ((self.contactDetailsMode == ContactDetailsModeFromChat) || ((self.contactDetailsMode == ContactDetailsModeDefault) && (self.user.visibility != MEGAUserVisibilityVisible))) { + self.navigationItem.leftBarButtonItem = self.backBarButtonItem; + } + //TODO: Show the blue check if the Contact is verified self.nameLabel.text = self.userName; From fecd4c343588cad9d99764a178e3e7949c5429c4 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 13:01:31 +0100 Subject: [PATCH 42/58] Fixed displaying wrong message in permissions alert --- iMEGA/Utils/DevicePermissionsHelper.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index f5b1ee2690..e022c53443 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -92,7 +92,7 @@ + (void)alertPhotosPermission { } + (void)alertPermissionWithMessage:(NSString *)message completionHandler:(void (^)(void))handler { - UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"photoLibraryPermissions", @"Alert message to explain that the MEGA app needs permission to access your device photos") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *permissionsAlertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:message preferredStyle:UIAlertControllerStyleAlert]; [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; [permissionsAlertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { From 514aaf584cb6312caec2e9ed70a04394c8f719c6 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 14:18:34 +0100 Subject: [PATCH 43/58] Some UI fixes - Removed the "Create Account" button from the Login view. - Support only portrait orientation in the initial launch view for iPhone. --- iMEGA/Launch/InitialLaunchViewController.m | 8 ++++++ iMEGA/Login/LoginViewController.m | 12 -------- iMEGA/Login/Main.storyboard | 33 ++++++---------------- 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index c315dc0624..2416d9f4e9 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -23,6 +23,14 @@ - (void)viewDidLoad { [self.skipButton setTitle:AMLocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; } +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if (UIDevice.currentDevice.iPhoneDevice) { + return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; + } + + return UIInterfaceOrientationMaskAll; +} + #pragma mark - Private - (void)performAnimation { diff --git a/iMEGA/Login/LoginViewController.m b/iMEGA/Login/LoginViewController.m index 65ee745a77..7c61095565 100644 --- a/iMEGA/Login/LoginViewController.m +++ b/iMEGA/Login/LoginViewController.m @@ -35,7 +35,6 @@ @interface LoginViewController () @property (weak, nonatomic) IBOutlet UIButton *loginButton; -@property (weak, nonatomic) IBOutlet UIButton *createAccountButton; @property (weak, nonatomic) IBOutlet UIButton *forgotPasswordButton; @end @@ -75,7 +74,6 @@ - (void)viewDidLoad { [self.loginButton setTitle:AMLocalizedString(@"login", @"Login") forState:UIControlStateNormal]; - [self.createAccountButton setTitle:AMLocalizedString(@"createAccount", nil) forState:UIControlStateNormal]; NSString *forgotPasswordString = AMLocalizedString(@"forgotPassword", @"An option to reset the password."); forgotPasswordString = [forgotPasswordString stringByReplacingOccurrencesOfString:@"?" withString:@""]; forgotPasswordString = [forgotPasswordString stringByReplacingOccurrencesOfString:@"¿" withString:@""]; @@ -227,16 +225,6 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } -#pragma mark - UIViewController - -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - if ([segue.identifier isEqualToString:@"CreateAccountStoryboardSegueID"] && [sender isKindOfClass:[NSString class]]) { - MEGANavigationController *createAccountNC = (MEGANavigationController *)segue.destinationViewController; - CreateAccountViewController *createAccountVC = (CreateAccountViewController *)createAccountNC.childViewControllers.firstObject; - [createAccountVC setEmailString:sender]; - } -} - #pragma mark - UITextFieldDelegate - (void)textFieldDidBeginEditing:(UITextField *)textField { diff --git a/iMEGA/Login/Main.storyboard b/iMEGA/Login/Main.storyboard index a0f9bac34e..a9c8494803 100644 --- a/iMEGA/Login/Main.storyboard +++ b/iMEGA/Login/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -89,7 +89,7 @@ - + @@ -110,7 +110,7 @@ - + @@ -131,7 +131,7 @@ - + - - - @@ -193,6 +177,7 @@ + @@ -200,7 +185,6 @@ - @@ -221,7 +205,6 @@ - From a847401ebe657d6e972d38fdec8f88ac89a17516 Mon Sep 17 00:00:00 2001 From: Carlos Martin Acera Date: Wed, 8 Aug 2018 09:29:36 +0200 Subject: [PATCH 44/58] Cherry pick 'Initial commit for group calls adapting code to new bindings' 16443b036f11f439c51ba550d5790e79dd688184 - This solves some compilation issues after merging the groupchat-call branch in Karere 0f2aea17ce60a973fad2b2c9a84d2f5ef64ff87a. SDK submodule update 4e845fac6914f4e18e4f51138eb7cf3f01941926 Karere submodule update eb9d5a10a2bc2d365de0cdbb03a82d29afdbfa33 --- iMEGA/Chat/CallViewController.m | 44 ++++++++++++++++-------------- iMEGA/Chat/MEGAProviderDelegate.m | 8 +++--- iMEGA/Login/MainTabBarController.m | 14 +++++----- iMEGA/Vendor/Karere | 2 +- iMEGA/Vendor/SDK | 2 +- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/iMEGA/Chat/CallViewController.m b/iMEGA/Chat/CallViewController.m index c54b0a645a..d1d1885c20 100644 --- a/iMEGA/Chat/CallViewController.m +++ b/iMEGA/Chat/CallViewController.m @@ -71,7 +71,7 @@ - (void)viewDidLoad { self.localVideoImageView.layer.masksToBounds = YES; self.localVideoImageView.layer.cornerRadius = 4; self.localVideoImageView.corner = CornerTopRight; - self.localVideoImageView.userInteractionEnabled = self.call.hasRemoteVideo; + self.localVideoImageView.userInteractionEnabled = self.call.hasVideoInitialCall; if (self.callType == CallTypeIncoming) { self.outgoingCallView.hidden = YES; @@ -128,8 +128,8 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[MEGASdkManager sharedMEGAChatSdk] addChatCallDelegate:self]; if (self.videoCall) { - [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideoDelegate:self.remoteVideoImageView]; - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; } [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; } @@ -138,8 +138,8 @@ - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[MEGASdkManager sharedMEGAChatSdk] removeChatCallDelegate:self]; - [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideoDelegate:self.remoteVideoImageView]; - [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideoDelegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; [[UIDevice currentDevice] setProximityMonitoringEnabled:NO]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -156,10 +156,11 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id size.height); + self.remoteAvatarImageView.hidden = UIDevice.currentDevice.iPadDevice ? NO : size.width > size.height; } - if (viewWillChangeOrientation && self.call.hasLocalVideo && self.call.hasRemoteVideo) { + MEGAChatSession *chatSession = [self.call sessionForPeer:[self.chatRoom peerHandleAtIndex:0]]; + if (viewWillChangeOrientation && self.call.hasLocalVideo && chatSession.hasVideo) { [coordinator animateAlongsideTransition:^(id context) { [self.localVideoImageView rotate]; } completion:nil]; @@ -280,7 +281,7 @@ - (void)enablePasscodeIfNeeded { - (IBAction)acceptCallWithVideo:(UIButton *)sender { MEGAChatAnswerCallRequestDelegate *answerCallRequestDelegate = [[MEGAChatAnswerCallRequestDelegate alloc] initWithCompletion:^(MEGAChatError *error) { if (error.type == MEGAChatErrorTypeOk) { - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; self.enableDisableVideoButton.selected = YES; } else { [self dismissViewControllerAnimated:YES completion:nil]; @@ -330,14 +331,14 @@ - (IBAction)enableDisableVideo:(UIButton *)sender { if (error.type == MEGAChatErrorTypeOk) { if (sender.selected) { self.localVideoImageView.hidden = YES; - [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideoDelegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; if (self.remoteVideoImageView.hidden) { - self.remoteAvatarImageView.hidden = self.view.frame.size.width > self.view.frame.size.height; + self.remoteAvatarImageView.hidden = UIDevice.currentDevice.iPadDevice ? NO : self.view.frame.size.width > self.view.frame.size.height; } } else { self.remoteAvatarImageView.hidden = YES; self.localVideoImageView.hidden = NO; - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; } sender.selected = !sender.selected; self.loudSpeakerEnabled = sender.selected; @@ -385,7 +386,9 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { } if ([call hasChangedForType:MEGAChatCallChangeTypeSessionStatus]) { - if ([call sessionStatusForPeer:call.peerSessionStatusChange] == MEGAChatConnectionInProgress) { + MEGAChatSession *chatSession = [self.call sessionForPeer:[self.call peerSessionStatusChange]]; + + if (chatSession.status == MEGAChatSessionStatusInProgress) { if (!self.timer.isValid) { [self.player stop]; @@ -427,30 +430,31 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { self.incomingCallView.hidden = YES; if ([call hasChangedForType:MEGAChatCallChangeTypeRemoteAVFlags]) { - self.localVideoImageView.userInteractionEnabled = call.hasRemoteVideo; - if (call.hasRemoteVideo) { + MEGAChatSession *chatSession = [self.call sessionForPeer:[self.call peerSessionStatusChange]]; + self.localVideoImageView.userInteractionEnabled = chatSession.hasVideo; + if (chatSession.hasVideo) { if (self.remoteVideoImageView.hidden) { - [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideoDelegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; self.remoteVideoImageView.hidden = NO; self.remoteAvatarImageView.hidden = YES; } } else { if (!self.remoteVideoImageView.hidden) { - [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideoDelegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; self.remoteVideoImageView.hidden = YES; if (self.localVideoImageView.hidden) { - self.remoteAvatarImageView.hidden = self.view.frame.size.width > self.view.frame.size.height; + self.remoteAvatarImageView.hidden = UIDevice.currentDevice.iPadDevice ? NO : self.view.frame.size.width > self.view.frame.size.height; } [self.remoteAvatarImageView mnz_setImageForUserHandle:[self.chatRoom peerHandleAtIndex:0]]; } } - [self.localVideoImageView remoteVideoEnable:call.remoteVideo]; - self.remoteMicImageView.hidden = call.hasRemoteAudio; + [self.localVideoImageView remoteVideoEnable:chatSession.hasVideo]; + self.remoteMicImageView.hidden = chatSession.hasAudio; } break; } - case MEGAChatCallStatusTerminating: + case MEGAChatCallStatusTerminatingUserParticipation: break; case MEGAChatCallStatusDestroyed: { diff --git a/iMEGA/Chat/MEGAProviderDelegate.m b/iMEGA/Chat/MEGAProviderDelegate.m index 41b3509141..785cabe58f 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.m +++ b/iMEGA/Chat/MEGAProviderDelegate.m @@ -42,7 +42,7 @@ - (instancetype)initWithMEGACallManager:(MEGACallManager *)megaCallManager { } - (void)reportIncomingCall:(MEGAChatCall *)call user:(MEGAUser *)user { - MEGALogDebug(@"[CallKit] Report incoming call %@ with uuid %@, video %@ and email %@", call, call.uuid, call.hasRemoteVideo ? @"YES" : @"NO", user.email); + MEGALogDebug(@"[CallKit] Report incoming call %@ with uuid %@, video %@ and email %@", call, call.uuid, call.hasVideoInitialCall ? @"YES" : @"NO", user.email); CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeEmailAddress value:user.email]; update.localizedCallerName = user.mnz_fullName; @@ -50,7 +50,7 @@ - (void)reportIncomingCall:(MEGAChatCall *)call user:(MEGAUser *)user { update.supportsGrouping = NO; update.supportsUngrouping = NO; update.supportsDTMF = NO; - update.hasVideo = call.hasRemoteVideo; + update.hasVideo = call.hasVideoInitialCall; [self.provider reportNewIncomingCallWithUUID:call.uuid update:update completion:^(NSError * _Nullable error) { if (error) { MEGALogError(@"Report new incoming call failed with error: %@", error); @@ -152,7 +152,7 @@ - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallActio update.supportsGrouping = NO; update.supportsUngrouping = NO; update.supportsDTMF = NO; - update.hasVideo = call.hasRemoteVideo; + update.hasVideo = call.hasVideoInitialCall; [provider reportCallWithUUID:action.callUUID updated:update]; @@ -172,7 +172,7 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct if (call) { CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:call.chatId]; - callVC.videoCall = call.hasRemoteVideo; + callVC.videoCall = call.hasVideoInitialCall; callVC.callType = CallTypeIncoming; callVC.megaCallManager = self.megaCallManager; callVC.call = call; diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index c17a295e65..a213c908eb 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -247,7 +247,7 @@ - (void)presentRingingCall:(MEGAChatSdk *)api call:(MEGAChatCall *)call { if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = chatRoom; - callVC.videoCall = call.hasRemoteVideo; + callVC.videoCall = call.hasVideoInitialCall; callVC.callType = CallTypeIncoming; [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } else { @@ -278,7 +278,7 @@ - (void)presentCallViewControllerIfThereIsAnIncomingCall { MEGAChatRoom *chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:call.chatId]; CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = chatRoom; - callVC.videoCall = call.hasRemoteVideo; + callVC.videoCall = call.hasVideoInitialCall; callVC.callType = CallTypeIncoming; [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } @@ -334,7 +334,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { [self.missedCallsDictionary setObject:call forKey:@(call.chatId)]; [DevicePermissionsHelper audioPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { - if (call.hasRemoteVideo) { + if (call.hasVideoInitialCall) { [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { [self presentRingingCall:api call:[api chatCallForCallId:call.callId]]; @@ -368,7 +368,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { [self.missedCallsDictionary removeObjectForKey:@(call.chatId)]; break; - case MEGAChatCallStatusTerminating: + case MEGAChatCallStatusTerminatingUserParticipation: break; case MEGAChatCallStatusDestroyed: if (call.isLocalTermCode) { @@ -380,7 +380,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getDeliveredNotificationsWithCompletionHandler:^(NSArray *notifications) { NSInteger missedVideoCalls, missedAudioCalls; - if (call.hasRemoteVideo) { + if (call.hasVideoInitialCall) { missedVideoCalls = 1; missedAudioCalls = 0; } else { @@ -392,7 +392,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { if ([[MEGASdk base64HandleForUserHandle:call.chatId] isEqualToString:notification.request.identifier]) { missedAudioCalls = [notification.request.content.userInfo[@"missedAudioCalls"] integerValue]; missedVideoCalls = [notification.request.content.userInfo[@"missedVideoCalls"] integerValue]; - if (call.hasRemoteVideo) { + if (call.hasVideoInitialCall) { missedVideoCalls++; } else { missedAudioCalls++; @@ -432,7 +432,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { } } - NSString *alertBody = [NSString mnz_stringByMissedAudioCalls:(call.hasRemoteVideo ? 0 : 1) andMissedVideoCalls:(call.hasRemoteVideo ? 1 : 0)]; + NSString *alertBody = [NSString mnz_stringByMissedAudioCalls:(call.hasVideoInitialCall ? 0 : 1) andMissedVideoCalls:(call.hasVideoInitialCall ? 1 : 0)]; UILocalNotification* localNotification = [[UILocalNotification alloc] init]; localNotification.alertTitle = @"MEGA"; localNotification.alertBody = [NSString stringWithFormat:@"%@: %@", chatRoom.title, alertBody]; diff --git a/iMEGA/Vendor/Karere b/iMEGA/Vendor/Karere index 7d73c2b9e5..eb9d5a10a2 160000 --- a/iMEGA/Vendor/Karere +++ b/iMEGA/Vendor/Karere @@ -1 +1 @@ -Subproject commit 7d73c2b9e5846a8cdcad6531ee93b2a249e0dfc2 +Subproject commit eb9d5a10a2bc2d365de0cdbb03a82d29afdbfa33 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 523b1b3724..4e845fac69 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 523b1b3724dfd15c48c100219fc4527ff50cdf7a +Subproject commit 4e845fac6914f4e18e4f51138eb7cf3f01941926 From f5fe3ff64dc8631c15eff73c7470eb8e5e711e88 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 17:16:47 +0100 Subject: [PATCH 45/58] Some UI fixes - The chat toolbar was never shown again after being hidden when tapping on the Camera button without camera permissions. - Updated submodule --- iMEGA/Chat/MessagesViewController.m | 1 + iMEGA/Vendor/JSQMessagesViewController | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index b6c6f154ae..8a14d8f897 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -1528,6 +1528,7 @@ - (void)didPressAccessoryButton:(UIButton *)sender { }]; } else { [DevicePermissionsHelper alertVideoPermissionWithCompletionHandler:nil]; + self.inputToolbar.hidden = NO; } }]; diff --git a/iMEGA/Vendor/JSQMessagesViewController b/iMEGA/Vendor/JSQMessagesViewController index 7b2eeb9370..8b54f0c60d 160000 --- a/iMEGA/Vendor/JSQMessagesViewController +++ b/iMEGA/Vendor/JSQMessagesViewController @@ -1 +1 @@ -Subproject commit 7b2eeb93701c3b4a91445a237d2fd639b726a83b +Subproject commit 8b54f0c60d5a0021daed90eab54a7b9efea9455b From 317aaf6aa64c46a83480c71c17fb4ac8c68305c0 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Wed, 9 Jan 2019 18:20:19 +0100 Subject: [PATCH 46/58] Center the labels vertically in the initial launch view --- iMEGA/Launch/InitialLaunchViewController.m | 39 ++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index 2416d9f4e9..88acb01ce9 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -10,6 +10,8 @@ @interface InitialLaunchViewController () @property (weak, nonatomic) IBOutlet UIButton *setupButton; @property (weak, nonatomic) IBOutlet UIButton *skipButton; +@property (nonatomic) BOOL logoMoved; + @end @implementation InitialLaunchViewController @@ -23,6 +25,20 @@ - (void)viewDidLoad { [self.skipButton setTitle:AMLocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; } +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + if (!self.logoMoved) { + return; + } + + [coordinator animateAlongsideTransition:^(id _Nonnull context) { + [self moveLogo]; + } completion:^(id _Nonnull context) { + [self centerLabels]; + }]; +} + - (UIInterfaceOrientationMask)supportedInterfaceOrientations { if (UIDevice.currentDevice.iPhoneDevice) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; @@ -36,15 +52,34 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - (void)performAnimation { [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.circularShapeLayer.hidden = YES; - CGFloat newY = MIN(self.logoImageView.frame.origin.x, 145.0f); - self.logoImageView.frame = CGRectMake(self.logoImageView.frame.origin.x, newY, self.logoImageView.frame.size.width, self.logoImageView.frame.size.height); + [self moveLogo]; } completion:^(BOOL finished) { + [self centerLabels]; self.titleLabel.hidden = self.descriptionLabel.hidden = NO; self.setupButton.hidden = self.skipButton.hidden = NO; self.activityIndicatorView.hidden = YES; + self.logoMoved = YES; }]; } +- (void)moveLogo { + CGFloat newY = MIN(self.logoImageView.frame.origin.x, 145.0f); + self.logoImageView.frame = CGRectMake(self.logoImageView.frame.origin.x, newY, self.logoImageView.frame.size.width, self.logoImageView.frame.size.height); +} + +- (void)centerLabels { + CGFloat bottomSeparation = 28.0f; + CGFloat verticalIncrement = (self.titleLabel.frame.origin.y - (self.logoImageView.frame.origin.y + self.logoImageView.frame.size.height) - bottomSeparation) / 2; + + CGRect titleFrame = self.titleLabel.frame; + titleFrame.origin.y -= verticalIncrement; + self.titleLabel.frame = titleFrame; + + CGRect descriptionFrame = self.descriptionLabel.frame; + descriptionFrame.origin.y -= verticalIncrement; + self.descriptionLabel.frame = descriptionFrame; +} + #pragma mark - IBActions - (IBAction)setupButtonPressed:(UIButton *)sender { From c50b4333279c74af4404ed841aedcb72215bbdb0 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Wed, 9 Jan 2019 19:26:05 +0100 Subject: [PATCH 47/58] Remove unused header --- iMEGA/MEGA-Bridging-Header.h | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 iMEGA/MEGA-Bridging-Header.h diff --git a/iMEGA/MEGA-Bridging-Header.h b/iMEGA/MEGA-Bridging-Header.h deleted file mode 100644 index 76ddb37ff4..0000000000 --- a/iMEGA/MEGA-Bridging-Header.h +++ /dev/null @@ -1,9 +0,0 @@ - -#import -#import "LocalizationSystem.h" - -#import "DevicePermissionsHelper.h" - -#import "UIColor+MNZCategory.h" -#import "UIDevice+MNZCategory.h" -#import "UIFont+MNZCategory.h" From 23f8b76610e78a27d12c090cbc6b57851a7d69d2 Mon Sep 17 00:00:00 2001 From: Javier Santana Espejo Date: Thu, 10 Jan 2019 11:33:37 +0100 Subject: [PATCH 48/58] Minor changes - Added a delay of one second to avoid presenting immediately the Camera Uploads Pop Up when tapping on its tab. - Renamed the method to instanciate OnboardingViewController. - Removed default image from xib. - Some code styling. JSQMessagesViewController submodule update bc001e4ce1bffdca2d157565781383fd9ef7fc6e --- iMEGA/API/Requests/MEGALoginRequestDelegate.m | 2 +- iMEGA/AppDelegate.m | 12 ++++++------ .../CameraUploadsPopUpViewController.m | 4 ++-- iMEGA/Camera uploads/PhotosViewController.m | 7 ++++--- iMEGA/Categories/MEGANode+MNZCategory.m | 12 ++++++------ iMEGA/Chat/ChatRoomsViewController.m | 4 ++-- iMEGA/Chat/MessagesViewController.m | 6 +++--- iMEGA/Cloud drive/CloudDriveViewController.m | 4 ++-- iMEGA/Launch/InitialLaunchViewController.m | 2 +- iMEGA/Launch/LaunchViewController.m | 4 ++-- iMEGA/Links/FolderLinkViewController.m | 4 ++-- iMEGA/My Account/MyAccountBaseViewController.m | 4 ++-- .../CameraUploadsTableViewController.m | 4 ++-- .../Language/LanguageTableViewController.m | 6 +++--- iMEGA/Utils/DevicePermissionsHelper.m | 15 +++++++++------ iMEGA/Utils/Helper.m | 6 +++--- .../OnboardingView/OnboardingView.xib | 5 +---- .../OnboardingViewController.h | 2 +- .../OnboardingViewController.m | 9 +++++---- iMEGA/Vendor/JSQMessagesViewController | 2 +- 20 files changed, 58 insertions(+), 56 deletions(-) diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.m b/iMEGA/API/Requests/MEGALoginRequestDelegate.m index b7015bb5fc..7be0710776 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.m @@ -130,7 +130,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [[MEGAStore shareInstance] configureMEGAStore]; LaunchViewController *launchVC; - if ([DevicePermissionsHelper shouldSetupPermissions]) { + if (DevicePermissionsHelper.shouldSetupPermissions) { launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"InitialLaunchViewControllerID"]; } else { launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:nil] instantiateViewControllerWithIdentifier:@"LaunchViewControllerID"]; diff --git a/iMEGA/AppDelegate.m b/iMEGA/AppDelegate.m index a9f7d96bd4..1e54b2aa04 100644 --- a/iMEGA/AppDelegate.m +++ b/iMEGA/AppDelegate.m @@ -327,7 +327,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( createAccountRequestDelegate.resumeCreateAccount = YES; [[MEGASdkManager sharedMEGASdk] resumeCreateAccountWithSessionId:sessionId delegate:createAccountRequestDelegate]; } else { - self.window.rootViewController = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; + self.window.rootViewController = [OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault]; } } @@ -861,10 +861,10 @@ - (void)showMainTabBar { if (isAccountFirstLogin) { isAccountFirstLogin = NO; if (self.isNewAccount) { - if ([MEGAPurchase sharedInstance].products.count > 0) { + if (MEGAPurchase.sharedInstance.products.count > 0) { [self showChooseAccountType]; } else { - [[MEGAPurchase sharedInstance] setPricingsDelegate:self]; + [MEGAPurchase.sharedInstance setPricingsDelegate:self]; self.chooseAccountTypeLater = YES; } self.newAccount = NO; @@ -893,9 +893,9 @@ - (void)showMainTabBar { } - (void)showOnboarding { - OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; + OnboardingViewController *onboardingVC = [OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault]; dispatch_async(dispatch_get_main_queue(), ^{ - UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; + UIView *overlayView = [UIScreen.mainScreen snapshotViewAfterScreenUpdates:NO]; [onboardingVC.view addSubview:overlayView]; self.window.rootViewController = onboardingVC; @@ -944,7 +944,7 @@ - (void)registerForVoIPNotifications { - (void)registerForNotifications { UNUserNotificationCenter.currentNotificationCenter.delegate = self; - if (![DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + if (!DevicePermissionsHelper.shouldAskForNotificationsPermissions) { [DevicePermissionsHelper notificationsPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { [[UIApplication sharedApplication] registerForRemoteNotifications]; diff --git a/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m b/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m index 551dce2d7d..3776e0a97b 100644 --- a/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m +++ b/iMEGA/Camera uploads/CameraUploadsPopUpViewController.m @@ -50,8 +50,8 @@ - (IBAction)enableTouchUpInside:(UIButton *)sender { [NSUserDefaults.standardUserDefaults setObject:@(granted) forKey:kIsCameraUploadsEnabled]; if (granted) { MEGALogInfo(@"Enable Camera Uploads"); - [CameraUploads syncManager].isCameraUploadsEnabled = YES; - [CameraUploads syncManager].isUploadVideosEnabled = YES; + CameraUploads.syncManager.isCameraUploadsEnabled = YES; + CameraUploads.syncManager.isUploadVideosEnabled = YES; [NSUserDefaults.standardUserDefaults setObject:@1 forKey:kIsUploadVideosEnabled]; [self dismissViewControllerAnimated:YES completion:^{ diff --git a/iMEGA/Camera uploads/PhotosViewController.m b/iMEGA/Camera uploads/PhotosViewController.m index 984237e297..7aa8cdb07a 100644 --- a/iMEGA/Camera uploads/PhotosViewController.m +++ b/iMEGA/Camera uploads/PhotosViewController.m @@ -110,9 +110,10 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (![NSUserDefaults.standardUserDefaults objectForKey:kIsCameraUploadsEnabled]) { - MEGANavigationController *cameraUploadsNavigationController = [[UIStoryboard storyboardWithName:@"Photos" bundle:nil] instantiateViewControllerWithIdentifier:@"CameraUploadsPopUpNavigationControllerID"]; - - [self presentViewController:cameraUploadsNavigationController animated:YES completion:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + MEGANavigationController *cameraUploadsNavigationController = [[UIStoryboard storyboardWithName:@"Photos" bundle:nil] instantiateViewControllerWithIdentifier:@"CameraUploadsPopUpNavigationControllerID"]; + [self presentViewController:cameraUploadsNavigationController animated:YES completion:nil]; + }); } } diff --git a/iMEGA/Categories/MEGANode+MNZCategory.m b/iMEGA/Categories/MEGANode+MNZCategory.m index 3454b60462..7bcdab0416 100644 --- a/iMEGA/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Categories/MEGANode+MNZCategory.m @@ -222,15 +222,15 @@ - (void)mnz_saveToPhotosWithApi:(MEGASdk *)api { if (granted) { [SVProgressHUD showImage:[UIImage imageNamed:@"saveToPhotos"] status:AMLocalizedString(@"Saving to Photos…", @"Text shown when starting the process to save a photo or video to Photos app")]; NSString *temporaryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:self.base64Handle] stringByAppendingPathComponent:self.name]; - NSString *temporaryFingerprint = [[MEGASdkManager sharedMEGASdk] fingerprintForFilePath:temporaryPath]; + NSString *temporaryFingerprint = [MEGASdkManager.sharedMEGASdk fingerprintForFilePath:temporaryPath]; if ([temporaryFingerprint isEqualToString:self.fingerprint]) { [self mnz_copyToGalleryFromTemporaryPath:temporaryPath]; - } else if ([MEGAReachabilityManager isReachableHUDIfNot]) { + } else if (MEGAReachabilityManager.isReachableHUDIfNot) { NSString *downloadsDirectory = [NSFileManager.defaultManager downloadsDirectory]; downloadsDirectory = downloadsDirectory.mnz_relativeLocalPath; - NSString *offlineNameString = [[MEGASdkManager sharedMEGASdkFolder] escapeFsIncompatible:self.name]; + NSString *offlineNameString = [MEGASdkManager.sharedMEGASdkFolder escapeFsIncompatible:self.name]; NSString *localPath = [downloadsDirectory stringByAppendingPathComponent:offlineNameString]; - [[MEGASdkManager sharedMEGASdk] startDownloadNode:[api authorizeNode:self] localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp]]; + [MEGASdkManager.sharedMEGASdk startDownloadNode:[api authorizeNode:self] localPath:localPath appData:[[NSString new] mnz_appDataToSaveInPhotosApp]]; } } else { [DevicePermissionsHelper alertPhotosPermission]; @@ -412,7 +412,7 @@ - (void)mnz_fileLinkDownloadFromViewController:(UIViewController *)viewControlle MEGALinkManager.selectedOption = LinkOptionDownloadNode; } - OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; + OnboardingViewController *onboardingVC = [OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault]; if (viewController.navigationController) { [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { @@ -444,7 +444,7 @@ - (void)mnz_fileLinkImportFromViewController:(UIViewController *)viewController MEGALinkManager.selectedOption = LinkOptionImportNode; } - OnboardingViewController *onboardingVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault]; + OnboardingViewController *onboardingVC = [OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault]; if (viewController.navigationController) { [viewController.navigationController pushViewController:onboardingVC animated:YES]; } else { diff --git a/iMEGA/Chat/ChatRoomsViewController.m b/iMEGA/Chat/ChatRoomsViewController.m index a662ed4c64..3f7744cf39 100644 --- a/iMEGA/Chat/ChatRoomsViewController.m +++ b/iMEGA/Chat/ChatRoomsViewController.m @@ -129,10 +129,10 @@ - (void)viewWillAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - NSInteger unreadChats = [MEGASdkManager sharedMEGAChatSdk] != nil ? [MEGASdkManager sharedMEGAChatSdk].unreadChats : 0; + NSInteger unreadChats = MEGASdkManager.sharedMEGAChatSdk ? MEGASdkManager.sharedMEGAChatSdk.unreadChats : 0; if (unreadChats > 0) { if (![NSUserDefaults.standardUserDefaults boolForKey:@"notificationsPermissionModalShown"]) { - if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { [DevicePermissionsHelper modalNotificationsPermission]; } diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index 8a14d8f897..2487200949 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -1220,7 +1220,7 @@ - (MEGAChatMessage *)sendMessage:(NSString *)text { [self finishSendingMessageAnimated:YES]; if (![NSUserDefaults.standardUserDefaults boolForKey:@"notificationsPermissionModalShown"]) { - if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { [DevicePermissionsHelper modalNotificationsPermission]; } @@ -1521,8 +1521,8 @@ - (void)didPressAccessoryButton:(UIButton *)sender { if (granted) { [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } else { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [NSUserDefaults.standardUserDefaults synchronize]; [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } }]; diff --git a/iMEGA/Cloud drive/CloudDriveViewController.m b/iMEGA/Cloud drive/CloudDriveViewController.m index 5cb4020d1e..d01810c977 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController.m +++ b/iMEGA/Cloud drive/CloudDriveViewController.m @@ -994,8 +994,8 @@ - (void)presentUploadAlertController { if (granted) { [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } else { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [NSUserDefaults.standardUserDefaults synchronize]; [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } }]; diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index 88acb01ce9..6570a464f9 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -83,7 +83,7 @@ - (void)centerLabels { #pragma mark - IBActions - (IBAction)setupButtonPressed:(UIButton *)sender { - OnboardingViewController *setupVC = [OnboardingViewController onboardingViewControllerOfType:OnboardingTypePermissions]; + OnboardingViewController *setupVC = [OnboardingViewController instanciateOnboardingWithType:OnboardingTypePermissions]; setupVC.completion = ^{ [self.delegate setupFinished]; }; diff --git a/iMEGA/Launch/LaunchViewController.m b/iMEGA/Launch/LaunchViewController.m index a764c5b021..569d514994 100644 --- a/iMEGA/Launch/LaunchViewController.m +++ b/iMEGA/Launch/LaunchViewController.m @@ -30,13 +30,13 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [[MEGASdkManager sharedMEGASdk] addMEGARequestDelegate:self]; + [MEGASdkManager.sharedMEGASdk addMEGARequestDelegate:self]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - [[MEGASdkManager sharedMEGASdk] removeMEGARequestDelegate:self]; + [MEGASdkManager.sharedMEGASdk removeMEGARequestDelegate:self]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { diff --git a/iMEGA/Links/FolderLinkViewController.m b/iMEGA/Links/FolderLinkViewController.m index 88d2ea75bd..bc81f8a37c 100644 --- a/iMEGA/Links/FolderLinkViewController.m +++ b/iMEGA/Links/FolderLinkViewController.m @@ -552,7 +552,7 @@ - (IBAction)downloadAction:(UIBarButtonItem *)sender { } MEGALinkManager.selectedOption = LinkOptionDownloadFolderOrNodes; - [self.navigationController pushViewController:[OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault] animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault] animated:YES]; } } @@ -584,7 +584,7 @@ - (IBAction)importAction:(UIBarButtonItem *)sender { } MEGALinkManager.selectedOption = LinkOptionImportFolderOrNodes; - [self.navigationController pushViewController:[OnboardingViewController onboardingViewControllerOfType:OnboardingTypeDefault] animated:YES]; + [self.navigationController pushViewController:[OnboardingViewController instanciateOnboardingWithType:OnboardingTypeDefault] animated:YES]; } return; diff --git a/iMEGA/My Account/MyAccountBaseViewController.m b/iMEGA/My Account/MyAccountBaseViewController.m index b51d2acd48..6a4451b9fe 100644 --- a/iMEGA/My Account/MyAccountBaseViewController.m +++ b/iMEGA/My Account/MyAccountBaseViewController.m @@ -129,8 +129,8 @@ - (void)presentChangeAvatarAlertController { if (granted) { [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } else { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"isSaveMediaCapturedToGalleryEnabled"]; + [NSUserDefaults.standardUserDefaults synchronize]; [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera]; } }]; diff --git a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m index c2ed182820..35501aca74 100644 --- a/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m +++ b/iMEGA/Settings/Camera Uploads/CameraUploadsTableViewController.m @@ -89,7 +89,7 @@ - (IBAction)enableCameraUploadsSwitchValueChanged:(UISwitch *)sender { [DevicePermissionsHelper photosPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { BOOL isCameraUploadsEnabled = ![CameraUploads syncManager].isCameraUploadsEnabled; - [[CameraUploads syncManager] setIsCameraUploadsEnabled:isCameraUploadsEnabled]; + CameraUploads.syncManager.isCameraUploadsEnabled = isCameraUploadsEnabled; [NSUserDefaults.standardUserDefaults setObject:@(isCameraUploadsEnabled) forKey:kIsCameraUploadsEnabled]; if (isCameraUploadsEnabled) { @@ -104,7 +104,7 @@ - (IBAction)enableCameraUploadsSwitchValueChanged:(UISwitch *)sender { [DevicePermissionsHelper alertPhotosPermission]; MEGALogInfo(@"Disable Camera Uploads"); - [[CameraUploads syncManager] setIsCameraUploadsEnabled:NO]; + CameraUploads.syncManager.isCameraUploadsEnabled = NO; [self.uploadVideosSwitch setOn:NO animated:YES]; [self.useCellularConnectionSwitch setOn:NO animated:YES]; diff --git a/iMEGA/Settings/Language/LanguageTableViewController.m b/iMEGA/Settings/Language/LanguageTableViewController.m index b2053f36f7..2e9f5c11a3 100644 --- a/iMEGA/Settings/Language/LanguageTableViewController.m +++ b/iMEGA/Settings/Language/LanguageTableViewController.m @@ -90,7 +90,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath // Schedule a notification to make it easy to reopen MEGA: NSString *notificationText = AMLocalizedString(@"languageRestartNotification", @"Text shown in a notification to make it easy for the user to restart the app after the language is changed"); - if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { exit(0); } else { [DevicePermissionsHelper notificationsPermissionWithCompletionHandler:^(BOOL granted) { @@ -98,7 +98,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (granted) { UNMutableNotificationContent *content = [UNMutableNotificationContent new]; content.body = notificationText; - content.sound = [UNNotificationSound defaultSound]; + content.sound = UNNotificationSound.defaultSound; UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO]; NSString *identifier = @"nz.mega"; @@ -114,7 +114,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath UILocalNotification* localNotification = [[UILocalNotification alloc] init]; localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1]; localNotification.alertBody = notificationText; - localNotification.timeZone = [NSTimeZone defaultTimeZone]; + localNotification.timeZone = NSTimeZone.defaultTimeZone; [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; // The exit must be called some time after the previous method is called, because there is no way to // know when the notification is properly scheduled, and calling exit inmediatley causes to not have diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index e022c53443..b41d5fc904 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -14,7 +14,7 @@ @implementation DevicePermissionsHelper #pragma mark - Permissions requests + (void)audioPermissionModal:(BOOL)modal forIncomingCall:(BOOL)incomingCall withCompletionHandler:(void (^)(BOOL granted))handler { - if (modal && [self shouldAskForAudioPermissions]) { + if (modal && self.shouldAskForAudioPermissions) { [self modalAudioPermissionForIncomingCall:incomingCall withCompletionHandler:handler]; } else { [self audioPermissionWithCompletionHandler:handler]; @@ -165,6 +165,7 @@ + (BOOL)shouldAskForAudioPermissions { if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusNotDetermined; } + return NO; } @@ -172,11 +173,12 @@ + (BOOL)shouldAskForVideoPermissions { if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType:completionHandler:)]) { return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusNotDetermined; } + return NO; } + (BOOL)shouldAskForPhotosPermissions { - return [PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusNotDetermined; + return PHPhotoLibrary.authorizationStatus == PHAuthorizationStatusNotDetermined; } + (BOOL)shouldAskForNotificationsPermissions { @@ -195,14 +197,15 @@ + (BOOL)shouldAskForNotificationsPermissions { } else { shouldAskForNotificationsPermissions = !UIApplication.sharedApplication.isRegisteredForRemoteNotifications; } + return shouldAskForNotificationsPermissions; } + (BOOL)shouldSetupPermissions { - BOOL shouldAskForAudioPermissions = [self shouldAskForAudioPermissions]; - BOOL shouldAskForVideoPermissions = [self shouldAskForVideoPermissions]; - BOOL shouldAskForPhotosPermissions = [self shouldAskForPhotosPermissions]; - BOOL shouldAskForNotificationsPermissions = [self shouldAskForNotificationsPermissions]; + BOOL shouldAskForAudioPermissions = self.shouldAskForAudioPermissions; + BOOL shouldAskForVideoPermissions = self.shouldAskForVideoPermissions; + BOOL shouldAskForPhotosPermissions = self.shouldAskForPhotosPermissions; + BOOL shouldAskForNotificationsPermissions = self.shouldAskForNotificationsPermissions; return shouldAskForAudioPermissions || shouldAskForVideoPermissions || shouldAskForPhotosPermissions || shouldAskForNotificationsPermissions; } diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index ee515020c7..18cf10b5e3 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -1330,13 +1330,13 @@ + (void)resetUserData { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"TransfersPaused"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kIsCameraUploadsEnabled]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kIsUploadVideosEnabled]; + [NSUserDefaults.standardUserDefaults removeObjectForKey:kIsCameraUploadsEnabled]; + [NSUserDefaults.standardUserDefaults removeObjectForKey:kIsUploadVideosEnabled]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"IsSavePhotoToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"IsSaveVideoToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ChatVideoQuality"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"notificationsPermissionModalShown"]; + [NSUserDefaults.standardUserDefaults removeObjectForKey:@"notificationsPermissionModalShown"]; //Set default order on logout [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"SortOrderType"]; diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib index 56392c3be1..42f229de66 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.xib @@ -25,7 +25,7 @@ - + - - - diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h index 9cff7e74e5..4bbe2dedab 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.h @@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OnboardingViewController : UIViewController -+ (OnboardingViewController *)onboardingViewControllerOfType:(OnboardingType)type; ++ (OnboardingViewController *)instanciateOnboardingWithType:(OnboardingType)type; @property (nonatomic, copy) void (^completion)(void); diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m index 76b04ad0ae..46db78b480 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/OnboardingViewController.m @@ -20,9 +20,10 @@ @implementation OnboardingViewController #pragma mark - Initialization -+ (OnboardingViewController *)onboardingViewControllerOfType:(OnboardingType)type { ++ (OnboardingViewController *)instanciateOnboardingWithType:(OnboardingType)type { OnboardingViewController *onboardingViewController = [[UIStoryboard storyboardWithName:@"Onboarding" bundle:nil] instantiateViewControllerWithIdentifier:@"OnboardingViewControllerID"]; onboardingViewController.type = type; + return onboardingViewController; } @@ -68,7 +69,7 @@ - (void)viewDidLoad { if (self.scrollView.subviews.firstObject.subviews.count == 4) { [self.scrollView.subviews.firstObject.subviews.lastObject removeFromSuperview]; int nextIndex = 0; - if ([DevicePermissionsHelper shouldAskForPhotosPermissions]) { + if (DevicePermissionsHelper.shouldAskForPhotosPermissions) { OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; onboardingView.type = OnboardingViewTypePhotosPermission; nextIndex++; @@ -76,7 +77,7 @@ - (void)viewDidLoad { [self.scrollView.subviews.firstObject.subviews[nextIndex] removeFromSuperview]; } - if ([DevicePermissionsHelper shouldAskForAudioPermissions] || [DevicePermissionsHelper shouldAskForVideoPermissions]) { + if (DevicePermissionsHelper.shouldAskForAudioPermissions || DevicePermissionsHelper.shouldAskForVideoPermissions) { OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; onboardingView.type = OnboardingViewTypeMicrophoneAndCameraPermissions; nextIndex++; @@ -84,7 +85,7 @@ - (void)viewDidLoad { [self.scrollView.subviews.firstObject.subviews[nextIndex] removeFromSuperview]; } - if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { OnboardingView *onboardingView = self.scrollView.subviews.firstObject.subviews[nextIndex]; onboardingView.type = OnboardingViewTypeNotificationsPermission; nextIndex++; diff --git a/iMEGA/Vendor/JSQMessagesViewController b/iMEGA/Vendor/JSQMessagesViewController index 8b54f0c60d..bc001e4ce1 160000 --- a/iMEGA/Vendor/JSQMessagesViewController +++ b/iMEGA/Vendor/JSQMessagesViewController @@ -1 +1 @@ -Subproject commit 8b54f0c60d5a0021daed90eab54a7b9efea9455b +Subproject commit bc001e4ce1bffdca2d157565781383fd9ef7fc6e From d8c7172f244ff3f234e184584e1cdb012a7be272 Mon Sep 17 00:00:00 2001 From: Javier Santana Espejo Date: Thu, 10 Jan 2019 12:30:16 +0100 Subject: [PATCH 49/58] Revert "Cherry pick 'Initial commit for group calls adapting code to new bindings' 16443b036f11f439c51ba550d5790e79dd688184" This reverts commit a847401ebe657d6e972d38fdec8f88ac89a17516. --- iMEGA/Chat/CallViewController.m | 44 ++++++++++++++---------------- iMEGA/Chat/MEGAProviderDelegate.m | 8 +++--- iMEGA/Login/MainTabBarController.m | 14 +++++----- iMEGA/Vendor/Karere | 2 +- iMEGA/Vendor/SDK | 2 +- 5 files changed, 33 insertions(+), 37 deletions(-) diff --git a/iMEGA/Chat/CallViewController.m b/iMEGA/Chat/CallViewController.m index d1d1885c20..c54b0a645a 100644 --- a/iMEGA/Chat/CallViewController.m +++ b/iMEGA/Chat/CallViewController.m @@ -71,7 +71,7 @@ - (void)viewDidLoad { self.localVideoImageView.layer.masksToBounds = YES; self.localVideoImageView.layer.cornerRadius = 4; self.localVideoImageView.corner = CornerTopRight; - self.localVideoImageView.userInteractionEnabled = self.call.hasVideoInitialCall; + self.localVideoImageView.userInteractionEnabled = self.call.hasRemoteVideo; if (self.callType == CallTypeIncoming) { self.outgoingCallView.hidden = YES; @@ -128,8 +128,8 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[MEGASdkManager sharedMEGAChatSdk] addChatCallDelegate:self]; if (self.videoCall) { - [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideoDelegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; } [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; } @@ -138,8 +138,8 @@ - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[MEGASdkManager sharedMEGAChatSdk] removeChatCallDelegate:self]; - [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; - [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideoDelegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideoDelegate:self.localVideoImageView]; [[UIDevice currentDevice] setProximityMonitoringEnabled:NO]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -156,11 +156,10 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id size.height; + self.remoteAvatarImageView.hidden = (size.width > size.height); } - MEGAChatSession *chatSession = [self.call sessionForPeer:[self.chatRoom peerHandleAtIndex:0]]; - if (viewWillChangeOrientation && self.call.hasLocalVideo && chatSession.hasVideo) { + if (viewWillChangeOrientation && self.call.hasLocalVideo && self.call.hasRemoteVideo) { [coordinator animateAlongsideTransition:^(id context) { [self.localVideoImageView rotate]; } completion:nil]; @@ -281,7 +280,7 @@ - (void)enablePasscodeIfNeeded { - (IBAction)acceptCallWithVideo:(UIButton *)sender { MEGAChatAnswerCallRequestDelegate *answerCallRequestDelegate = [[MEGAChatAnswerCallRequestDelegate alloc] initWithCompletion:^(MEGAChatError *error) { if (error.type == MEGAChatErrorTypeOk) { - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; self.enableDisableVideoButton.selected = YES; } else { [self dismissViewControllerAnimated:YES completion:nil]; @@ -331,14 +330,14 @@ - (IBAction)enableDisableVideo:(UIButton *)sender { if (error.type == MEGAChatErrorTypeOk) { if (sender.selected) { self.localVideoImageView.hidden = YES; - [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatLocalVideoDelegate:self.localVideoImageView]; if (self.remoteVideoImageView.hidden) { - self.remoteAvatarImageView.hidden = UIDevice.currentDevice.iPadDevice ? NO : self.view.frame.size.width > self.view.frame.size.height; + self.remoteAvatarImageView.hidden = self.view.frame.size.width > self.view.frame.size.height; } } else { self.remoteAvatarImageView.hidden = YES; self.localVideoImageView.hidden = NO; - [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideo:self.chatRoom.chatId delegate:self.localVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatLocalVideoDelegate:self.localVideoImageView]; } sender.selected = !sender.selected; self.loudSpeakerEnabled = sender.selected; @@ -386,9 +385,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { } if ([call hasChangedForType:MEGAChatCallChangeTypeSessionStatus]) { - MEGAChatSession *chatSession = [self.call sessionForPeer:[self.call peerSessionStatusChange]]; - - if (chatSession.status == MEGAChatSessionStatusInProgress) { + if ([call sessionStatusForPeer:call.peerSessionStatusChange] == MEGAChatConnectionInProgress) { if (!self.timer.isValid) { [self.player stop]; @@ -430,31 +427,30 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { self.incomingCallView.hidden = YES; if ([call hasChangedForType:MEGAChatCallChangeTypeRemoteAVFlags]) { - MEGAChatSession *chatSession = [self.call sessionForPeer:[self.call peerSessionStatusChange]]; - self.localVideoImageView.userInteractionEnabled = chatSession.hasVideo; - if (chatSession.hasVideo) { + self.localVideoImageView.userInteractionEnabled = call.hasRemoteVideo; + if (call.hasRemoteVideo) { if (self.remoteVideoImageView.hidden) { - [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] addChatRemoteVideoDelegate:self.remoteVideoImageView]; self.remoteVideoImageView.hidden = NO; self.remoteAvatarImageView.hidden = YES; } } else { if (!self.remoteVideoImageView.hidden) { - [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideo:self.chatRoom.chatId peerId:[self.chatRoom peerHandleAtIndex:0] delegate:self.remoteVideoImageView]; + [[MEGASdkManager sharedMEGAChatSdk] removeChatRemoteVideoDelegate:self.remoteVideoImageView]; self.remoteVideoImageView.hidden = YES; if (self.localVideoImageView.hidden) { - self.remoteAvatarImageView.hidden = UIDevice.currentDevice.iPadDevice ? NO : self.view.frame.size.width > self.view.frame.size.height; + self.remoteAvatarImageView.hidden = self.view.frame.size.width > self.view.frame.size.height; } [self.remoteAvatarImageView mnz_setImageForUserHandle:[self.chatRoom peerHandleAtIndex:0]]; } } - [self.localVideoImageView remoteVideoEnable:chatSession.hasVideo]; - self.remoteMicImageView.hidden = chatSession.hasAudio; + [self.localVideoImageView remoteVideoEnable:call.remoteVideo]; + self.remoteMicImageView.hidden = call.hasRemoteAudio; } break; } - case MEGAChatCallStatusTerminatingUserParticipation: + case MEGAChatCallStatusTerminating: break; case MEGAChatCallStatusDestroyed: { diff --git a/iMEGA/Chat/MEGAProviderDelegate.m b/iMEGA/Chat/MEGAProviderDelegate.m index 785cabe58f..41b3509141 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.m +++ b/iMEGA/Chat/MEGAProviderDelegate.m @@ -42,7 +42,7 @@ - (instancetype)initWithMEGACallManager:(MEGACallManager *)megaCallManager { } - (void)reportIncomingCall:(MEGAChatCall *)call user:(MEGAUser *)user { - MEGALogDebug(@"[CallKit] Report incoming call %@ with uuid %@, video %@ and email %@", call, call.uuid, call.hasVideoInitialCall ? @"YES" : @"NO", user.email); + MEGALogDebug(@"[CallKit] Report incoming call %@ with uuid %@, video %@ and email %@", call, call.uuid, call.hasRemoteVideo ? @"YES" : @"NO", user.email); CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeEmailAddress value:user.email]; update.localizedCallerName = user.mnz_fullName; @@ -50,7 +50,7 @@ - (void)reportIncomingCall:(MEGAChatCall *)call user:(MEGAUser *)user { update.supportsGrouping = NO; update.supportsUngrouping = NO; update.supportsDTMF = NO; - update.hasVideo = call.hasVideoInitialCall; + update.hasVideo = call.hasRemoteVideo; [self.provider reportNewIncomingCallWithUUID:call.uuid update:update completion:^(NSError * _Nullable error) { if (error) { MEGALogError(@"Report new incoming call failed with error: %@", error); @@ -152,7 +152,7 @@ - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallActio update.supportsGrouping = NO; update.supportsUngrouping = NO; update.supportsDTMF = NO; - update.hasVideo = call.hasVideoInitialCall; + update.hasVideo = call.hasRemoteVideo; [provider reportCallWithUUID:action.callUUID updated:update]; @@ -172,7 +172,7 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct if (call) { CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:call.chatId]; - callVC.videoCall = call.hasVideoInitialCall; + callVC.videoCall = call.hasRemoteVideo; callVC.callType = CallTypeIncoming; callVC.megaCallManager = self.megaCallManager; callVC.call = call; diff --git a/iMEGA/Login/MainTabBarController.m b/iMEGA/Login/MainTabBarController.m index a213c908eb..c17a295e65 100644 --- a/iMEGA/Login/MainTabBarController.m +++ b/iMEGA/Login/MainTabBarController.m @@ -247,7 +247,7 @@ - (void)presentRingingCall:(MEGAChatSdk *)api call:(MEGAChatCall *)call { if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = chatRoom; - callVC.videoCall = call.hasVideoInitialCall; + callVC.videoCall = call.hasRemoteVideo; callVC.callType = CallTypeIncoming; [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } else { @@ -278,7 +278,7 @@ - (void)presentCallViewControllerIfThereIsAnIncomingCall { MEGAChatRoom *chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:call.chatId]; CallViewController *callVC = [[UIStoryboard storyboardWithName:@"Chat" bundle:nil] instantiateViewControllerWithIdentifier:@"CallViewControllerID"]; callVC.chatRoom = chatRoom; - callVC.videoCall = call.hasVideoInitialCall; + callVC.videoCall = call.hasRemoteVideo; callVC.callType = CallTypeIncoming; [UIApplication.mnz_presentingViewController presentViewController:callVC animated:YES completion:nil]; } @@ -334,7 +334,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { [self.missedCallsDictionary setObject:call forKey:@(call.chatId)]; [DevicePermissionsHelper audioPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { - if (call.hasVideoInitialCall) { + if (call.hasRemoteVideo) { [DevicePermissionsHelper videoPermissionWithCompletionHandler:^(BOOL granted) { if (granted) { [self presentRingingCall:api call:[api chatCallForCallId:call.callId]]; @@ -368,7 +368,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { [self.missedCallsDictionary removeObjectForKey:@(call.chatId)]; break; - case MEGAChatCallStatusTerminatingUserParticipation: + case MEGAChatCallStatusTerminating: break; case MEGAChatCallStatusDestroyed: if (call.isLocalTermCode) { @@ -380,7 +380,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getDeliveredNotificationsWithCompletionHandler:^(NSArray *notifications) { NSInteger missedVideoCalls, missedAudioCalls; - if (call.hasVideoInitialCall) { + if (call.hasRemoteVideo) { missedVideoCalls = 1; missedAudioCalls = 0; } else { @@ -392,7 +392,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { if ([[MEGASdk base64HandleForUserHandle:call.chatId] isEqualToString:notification.request.identifier]) { missedAudioCalls = [notification.request.content.userInfo[@"missedAudioCalls"] integerValue]; missedVideoCalls = [notification.request.content.userInfo[@"missedVideoCalls"] integerValue]; - if (call.hasVideoInitialCall) { + if (call.hasRemoteVideo) { missedVideoCalls++; } else { missedAudioCalls++; @@ -432,7 +432,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { } } - NSString *alertBody = [NSString mnz_stringByMissedAudioCalls:(call.hasVideoInitialCall ? 0 : 1) andMissedVideoCalls:(call.hasVideoInitialCall ? 1 : 0)]; + NSString *alertBody = [NSString mnz_stringByMissedAudioCalls:(call.hasRemoteVideo ? 0 : 1) andMissedVideoCalls:(call.hasRemoteVideo ? 1 : 0)]; UILocalNotification* localNotification = [[UILocalNotification alloc] init]; localNotification.alertTitle = @"MEGA"; localNotification.alertBody = [NSString stringWithFormat:@"%@: %@", chatRoom.title, alertBody]; diff --git a/iMEGA/Vendor/Karere b/iMEGA/Vendor/Karere index eb9d5a10a2..7d73c2b9e5 160000 --- a/iMEGA/Vendor/Karere +++ b/iMEGA/Vendor/Karere @@ -1 +1 @@ -Subproject commit eb9d5a10a2bc2d365de0cdbb03a82d29afdbfa33 +Subproject commit 7d73c2b9e5846a8cdcad6531ee93b2a249e0dfc2 diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 4e845fac69..523b1b3724 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 4e845fac6914f4e18e4f51138eb7cf3f01941926 +Subproject commit 523b1b3724dfd15c48c100219fc4527ff50cdf7a From c9c22cae4044f0857fdd4cfd67b839ead5b0de10 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Thu, 10 Jan 2019 12:42:58 +0100 Subject: [PATCH 50/58] Hide the minutes when auto-away is turned off (11269) --- iMEGA/Settings/Chat/ChatSettings.storyboard | 1 + .../Settings/Chat/ChatStatusTableViewController.m | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/iMEGA/Settings/Chat/ChatSettings.storyboard b/iMEGA/Settings/Chat/ChatSettings.storyboard index d880f511ab..4dd2836e02 100644 --- a/iMEGA/Settings/Chat/ChatSettings.storyboard +++ b/iMEGA/Settings/Chat/ChatSettings.storyboard @@ -594,6 +594,7 @@ + diff --git a/iMEGA/Settings/Chat/ChatStatusTableViewController.m b/iMEGA/Settings/Chat/ChatStatusTableViewController.m index 75cc64675c..c0d24d66fc 100644 --- a/iMEGA/Settings/Chat/ChatStatusTableViewController.m +++ b/iMEGA/Settings/Chat/ChatStatusTableViewController.m @@ -35,6 +35,7 @@ @interface ChatStatusTableViewController () = 2) { - titleForFooter = AMLocalizedString(@"showMeAwayAfterXMinutesOfInactivity", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); - titleForFooter = [titleForFooter stringByReplacingOccurrencesOfString:@"[X]" withString:[NSString stringWithFormat:@"%lld", (self.presenceConfig.autoAwayTimeout / 60)]]; - } else { - titleForFooter = AMLocalizedString(@"showMeAwayAfter1MinuteOfInactivity", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); + if (self.presenceConfig.isAutoAwayEnabled) { + if ((self.presenceConfig.autoAwayTimeout / 60) >= 2) { + titleForFooter = AMLocalizedString(@"showMeAwayAfterXMinutesOfInactivity", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); + titleForFooter = [titleForFooter stringByReplacingOccurrencesOfString:@"[X]" withString:[NSString stringWithFormat:@"%lld", (self.presenceConfig.autoAwayTimeout / 60)]]; + } else { + titleForFooter = AMLocalizedString(@"showMeAwayAfter1MinuteOfInactivity", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); + } } break; } From 95cf0b7f82d471402e7e4172336072e9a2fb27f5 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Thu, 10 Jan 2019 12:44:18 +0100 Subject: [PATCH 51/58] Always ask for notifications permission after entering the chat tab having unread messages --- iMEGA/Chat/ChatRoomsViewController.m | 9 ++------- iMEGA/Chat/MessagesViewController.m | 9 --------- iMEGA/Utils/DevicePermissionsHelper.m | 3 +-- iMEGA/Utils/Helper.m | 2 -- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/iMEGA/Chat/ChatRoomsViewController.m b/iMEGA/Chat/ChatRoomsViewController.m index 3f7744cf39..97229ea0fa 100644 --- a/iMEGA/Chat/ChatRoomsViewController.m +++ b/iMEGA/Chat/ChatRoomsViewController.m @@ -131,13 +131,8 @@ - (void)viewDidAppear:(BOOL)animated { NSInteger unreadChats = MEGASdkManager.sharedMEGAChatSdk ? MEGASdkManager.sharedMEGAChatSdk.unreadChats : 0; if (unreadChats > 0) { - if (![NSUserDefaults.standardUserDefaults boolForKey:@"notificationsPermissionModalShown"]) { - if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { - [DevicePermissionsHelper modalNotificationsPermission]; - } - - [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"notificationsPermissionModalShown"]; - [NSUserDefaults.standardUserDefaults synchronize]; + if ([DevicePermissionsHelper shouldAskForNotificationsPermissions]) { + [DevicePermissionsHelper modalNotificationsPermission]; } } } diff --git a/iMEGA/Chat/MessagesViewController.m b/iMEGA/Chat/MessagesViewController.m index 260e299fed..473ff5c7a8 100644 --- a/iMEGA/Chat/MessagesViewController.m +++ b/iMEGA/Chat/MessagesViewController.m @@ -1218,15 +1218,6 @@ - (MEGAChatMessage *)sendMessage:(NSString *)text { dispatch_async(dispatch_get_main_queue(), ^{ [self finishSendingMessageAnimated:YES]; - - if (![NSUserDefaults.standardUserDefaults boolForKey:@"notificationsPermissionModalShown"]) { - if (DevicePermissionsHelper.shouldAskForNotificationsPermissions) { - [DevicePermissionsHelper modalNotificationsPermission]; - } - - [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"notificationsPermissionModalShown"]; - [NSUserDefaults.standardUserDefaults synchronize]; - } }); [[MEGASdkManager sharedMEGAChatSdk] sendStopTypingNotificationForChat:self.chatRoom.chatId]; diff --git a/iMEGA/Utils/DevicePermissionsHelper.m b/iMEGA/Utils/DevicePermissionsHelper.m index b41d5fc904..7e75e49fe1 100644 --- a/iMEGA/Utils/DevicePermissionsHelper.m +++ b/iMEGA/Utils/DevicePermissionsHelper.m @@ -135,8 +135,7 @@ + (void)modalNotificationsPermission { permissionsModal.image = [UIImage imageNamed:@"micAndCamPermission"]; permissionsModal.viewTitle = AMLocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); permissionsModal.detail = AMLocalizedString(@"We would like to send you notifications so you receive new messages on your device instantly.", @"Detailed explanation of why the user should give permission to deliver notifications"); - permissionsModal.action = AMLocalizedString(@"Allow Access", @"Button which triggers a request for a specific permission, that have been explained to the user beforehand"); - permissionsModal.dismiss = AMLocalizedString(@"notNow", nil); + permissionsModal.action = AMLocalizedString(@"continue", @"'Next' button in a dialog"); permissionsModal.completion = ^{ [self notificationsPermissionWithCompletionHandler:^(BOOL granted) { diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index 18cf10b5e3..c2cefcd847 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -1336,8 +1336,6 @@ + (void)resetUserData { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"IsSaveVideoToGalleryEnabled"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ChatVideoQuality"]; - [NSUserDefaults.standardUserDefaults removeObjectForKey:@"notificationsPermissionModalShown"]; - //Set default order on logout [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"SortOrderType"]; [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"OfflineSortOrderType"]; From b901e5aeef3db720448d1f652424d780c210c9db Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Thu, 10 Jan 2019 13:03:30 +0100 Subject: [PATCH 52/58] JSQMessagesViewController submodule update --- iMEGA/Vendor/JSQMessagesViewController | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iMEGA/Vendor/JSQMessagesViewController b/iMEGA/Vendor/JSQMessagesViewController index bc001e4ce1..5124c10f89 160000 --- a/iMEGA/Vendor/JSQMessagesViewController +++ b/iMEGA/Vendor/JSQMessagesViewController @@ -1 +1 @@ -Subproject commit bc001e4ce1bffdca2d157565781383fd9ef7fc6e +Subproject commit 5124c10f899df5f5356881e7b477d258bfb826b0 From 2ae48f0058495c93fa5d3dcfdaf4ba203eace30e Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Thu, 10 Jan 2019 13:06:56 +0100 Subject: [PATCH 53/58] Bump version --- iMEGA/Extensions/MEGAPicker/Info.plist | 4 ++-- iMEGA/Extensions/MEGAPickerFileProvider/Info.plist | 4 ++-- iMEGA/Extensions/MEGAShare/Info.plist | 4 ++-- iMEGA/Info.plist | 4 ++-- iMEGA/Settings/About/AboutTableViewController.m | 2 +- iMEGA/Vendor/SDK | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/iMEGA/Extensions/MEGAPicker/Info.plist b/iMEGA/Extensions/MEGAPicker/Info.plist index 54f7dc5847..2533863b26 100644 --- a/iMEGA/Extensions/MEGAPicker/Info.plist +++ b/iMEGA/Extensions/MEGAPicker/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.4.1 + 4.4.2 CFBundleVersion - 134 + 135 NSExtension NSExtensionAttributes diff --git a/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist b/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist index 8fd5a6ff9d..59c1ac9cf8 100644 --- a/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist +++ b/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.4.1 + 4.4.2 CFBundleVersion - 134 + 135 NSExtension NSExtensionFileProviderDocumentGroup diff --git a/iMEGA/Extensions/MEGAShare/Info.plist b/iMEGA/Extensions/MEGAShare/Info.plist index e4b7c332af..3b2d5e839c 100644 --- a/iMEGA/Extensions/MEGAShare/Info.plist +++ b/iMEGA/Extensions/MEGAShare/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.4.1 + 4.4.2 CFBundleVersion - 134 + 135 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/iMEGA/Info.plist b/iMEGA/Info.plist index 7b3ba4a6f2..7923af37c5 100644 --- a/iMEGA/Info.plist +++ b/iMEGA/Info.plist @@ -36,7 +36,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.4.1 + 4.4.2 CFBundleSignature ???? CFBundleURLTypes @@ -51,7 +51,7 @@ CFBundleVersion - 134 + 135 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/iMEGA/Settings/About/AboutTableViewController.m b/iMEGA/Settings/About/AboutTableViewController.m index 15327b6e29..3d1f097648 100644 --- a/iMEGA/Settings/About/AboutTableViewController.m +++ b/iMEGA/Settings/About/AboutTableViewController.m @@ -39,7 +39,7 @@ - (void)viewDidLoad { self.versionCell.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; self.sdkVersionLabel.text = AMLocalizedString(@"sdkVersion", @"Title of the label where the SDK version is shown"); - self.sdkVersionSHALabel.text = @"4ca0a05e"; + self.sdkVersionSHALabel.text = @"a01c429d"; self.megachatSdkVersionLabel.text = AMLocalizedString(@"megachatSdkVersion", @"Title of the label where the MEGAchat SDK version is shown"); self.megachatSdkSHALabel.text = @"9c9b9cce"; diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index 9ffe71a57d..a01c429d73 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit 9ffe71a57d008aa8fe7ff1f7e05657695d87ae9d +Subproject commit a01c429d73af8a722c190108059efe7d1c859632 From 90ebc697b1e0a043e3496c0e3b5b847367606ad2 Mon Sep 17 00:00:00 2001 From: Javier Santana Espejo Date: Mon, 14 Jan 2019 10:54:22 +0100 Subject: [PATCH 54/58] Check if the index path is valid before replacing the object on the transfer start for a download transfer (11288) --- iMEGA/Transfers/TransfersViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iMEGA/Transfers/TransfersViewController.m b/iMEGA/Transfers/TransfersViewController.m index 1e60108f7f..c9e3bcd77a 100644 --- a/iMEGA/Transfers/TransfersViewController.m +++ b/iMEGA/Transfers/TransfersViewController.m @@ -628,7 +628,9 @@ - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } } else if (transfer.type == MEGATransferTypeDownload) { NSIndexPath *indexPath = [self indexPathForTransfer:transfer]; - [self.transfers replaceObjectAtIndex:indexPath.row withObject:transfer]; + if (indexPath) { + [self.transfers replaceObjectAtIndex:indexPath.row withObject:transfer]; + } } } From 6d939b27fef4a621728dc38ed07ae173d95d3b16 Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Mon, 14 Jan 2019 17:57:25 +0100 Subject: [PATCH 55/58] Improvements in permissions priming views - Show a label instead of the page control. - Show a warning to the users that don't want to setup permissions after login. --- .../Languages/Base.lproj/Localizable.strings | 6 +++-- iMEGA/Languages/ar.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/de.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/en.lproj/Localizable.strings | 4 ++++ iMEGA/Languages/es.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/fr.lproj/Localizable.strings | 24 +++++++++++-------- iMEGA/Languages/he.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/id.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/it.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/ja.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/ko.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/nl.lproj/Localizable.strings | 8 +++++-- iMEGA/Languages/pl.lproj/Localizable.strings | 6 ++++- .../Languages/pt-br.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/ro.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/ru.lproj/Localizable.strings | 8 +++++-- iMEGA/Languages/th.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/tl.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/tr.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/uk.lproj/Localizable.strings | 6 ++++- iMEGA/Languages/vi.lproj/Localizable.strings | 6 ++++- .../zh-Hans.lproj/Localizable.strings | 18 ++++++++------ .../zh-Hant.lproj/Localizable.strings | 6 ++++- iMEGA/Launch/InitialLaunchViewController.m | 8 ++++++- .../Onboarding.storyboard | 9 +++++++ .../OnboardingViewController.m | 9 ++++--- 26 files changed, 152 insertions(+), 44 deletions(-) diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index 51d0f5477e..849f0d041d 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -1624,14 +1624,16 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 of %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ "Please give the MEGA App permission to access Photos to share photos and videos."="Please give the MEGA App permission to access Photos to share photos and videos."; /* Title label that explains that the user is going to be asked for the microphone and camera permission */ "Enable Microphone and Camera"="Enable Microphone and Camera"; -/* notification subtitle of incoming calls */ -"Incoming call"="Incoming call"; /* Detailed explanation of why the user should give permission to access to the camera and the microphone */ "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"="To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone"; /* Title label that explains that the user is going to be asked for the notifications permission */ diff --git a/iMEGA/Languages/ar.lproj/Localizable.strings b/iMEGA/Languages/ar.lproj/Localizable.strings index f57779a7a3..2117ae8268 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.strings +++ b/iMEGA/Languages/ar.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 من %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="اليوم"; /* */ -"Yesterday"="الأمس"; +"Yesterday"="الأمس"; \ No newline at end of file diff --git a/iMEGA/Languages/de.lproj/Localizable.strings b/iMEGA/Languages/de.lproj/Localizable.strings index 2468437854..5eaa927e03 100644 --- a/iMEGA/Languages/de.lproj/Localizable.strings +++ b/iMEGA/Languages/de.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 von %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Heute"; /* */ -"Yesterday"="Gestern"; +"Yesterday"="Gestern"; \ No newline at end of file diff --git a/iMEGA/Languages/en.lproj/Localizable.strings b/iMEGA/Languages/en.lproj/Localizable.strings index aedd5a7681..849f0d041d 100644 --- a/iMEGA/Languages/en.lproj/Localizable.strings +++ b/iMEGA/Languages/en.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 of %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ diff --git a/iMEGA/Languages/es.lproj/Localizable.strings b/iMEGA/Languages/es.lproj/Localizable.strings index 99a4e72b90..a1c3add6cf 100644 --- a/iMEGA/Languages/es.lproj/Localizable.strings +++ b/iMEGA/Languages/es.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Configurar MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para aprovechar al máximo tu cuenta MEGA, debemos pedirte algunos permisos."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Permitir el acceso a Fotos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Hoy"; /* */ -"Yesterday"="Ayer"; +"Yesterday"="Ayer"; \ No newline at end of file diff --git a/iMEGA/Languages/fr.lproj/Localizable.strings b/iMEGA/Languages/fr.lproj/Localizable.strings index 67de88dd7b..9f47818a88 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.strings +++ b/iMEGA/Languages/fr.lproj/Localizable.strings @@ -11,7 +11,7 @@ /* */ "error"="Erreur"; /* */ -"name"="Nom"; +"name"="Prénom"; /* Caption of a button to edit the files that are selected */ "edit"="Modifier"; /* State shown if something is 'Saved' (String as short as possible). */ @@ -129,7 +129,7 @@ /* */ "passwordStrong"="Ce mot de passe résistera à la plupart des attaques par force brute typiques. Veuillez vous assurer de vous en souvenir."; /* */ -"agreeWithTheMEGATermsOfService"="J'accepte les conditions générales d'utilisation de MEGA"; +"agreeWithTheMEGATermsOfService"="J’accepte les conditions générales d’utilisation de MEGA"; /* Error text shown when you have not entered a correct name */ "nameInvalidFormat"="Saisir un nom valide"; /* Error text shown when you have not written the same password */ @@ -203,7 +203,7 @@ /* Title shown when your Rubbish Bin is empty. */ "cloudDriveEmptyState_titleRubbishBin"="Vider la corbeille"; /* Title shown when the Offline section is empty, when you don't have download any files. Keep the upper. */ -"offlineEmptyState_title"="Aucun fichier enregistré Hors ligne"; +"offlineEmptyState_title"="Aucun fichier n’est enregistré Hors ligne"; /* Title shown when the Contacts section is empty, when you have not added any contact. */ "contactsEmptyState_title"="Aucun contact"; /* Title shown when there's no incoming Shared Items */ @@ -231,7 +231,7 @@ /* Success message shown when you remove a contact request */ "requestDeleted"="La demande a été supprimée"; /* Title shown when there's no pending contact requests */ -"noRequestPending"="Aucune requête en attente"; +"noRequestPending"="Il n’y a aucune requête en attente"; /* Title shown when the transfers are paused */ "transfersEmptyState_titlePaused"="Transferts en pause"; /* Title shown when the there is not any transfer and they are not paused */ @@ -511,9 +511,9 @@ /* Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA */ "enableCameraUploadsButton"="Activer les téléversements de la caméra"; /* Success message shown when Camera Uploads has been enabled */ -"cameraUploadsEnabled"="Les téléversements de la caméra sont activés"; +"cameraUploadsEnabled"="Les téléversements de l’appareil photo sont activés"; /* Message shown when the camera uploads have been completed */ -"cameraUploadsComplete"="Téléversement de la caméra terminé"; +"cameraUploadsComplete"="Les téléversements de l’appareil photo sont terminés"; /* Message shown while uploading files. Singular. */ "cameraUploadsPendingFile"="Téléversement en cours, 1 fichier en attente"; /* Message shown while uploading files. Plural. */ @@ -1058,13 +1058,13 @@ /* Alert message shown when the user remove various items from the Offline section */ "removeItemsFromOffline"="Voulez-vous vraiment supprimer ces éléments Hors ligne ?"; /* Chat section header */ -"chat"="Dialogue"; +"chat"="Dialogue en ligne"; /* Empty Conversations section */ "noConversations"="Aucune conversation"; /* Empty Conversations description */ "noConversationsDescription"="Commencez à discuter en toute sécurité avec vos contacts grâce au chiffrement de bout en bout"; /* Title show when the chat is disabled */ -"chatIsDisabled"="Le dialogue est désactivé"; +"chatIsDisabled"="Le dialogue en ligne est désactivé"; /* Text button shown when the chat is disabled and if tapped the chat will be enabled */ "enable"="Activer"; /* Text button shown when an option is enabled, to allow to disable it. String as sort as possible. */ @@ -1435,7 +1435,7 @@ /* Title of the incoming shared folders of a user in singular */ "sharedFolder"="Dossier partagé"; /* The label of the folder creation time. For example: "Created: 2013-02-02" (no need to put the colon punctuation in the translation) */ -"created"="Créé"; +"created"="Créé le"; /* Label for what a selection contains. For example: "Contains: 3 folders & 13 files". (no need to put the colon punctuation in the translation) */ "contains"="Contient"; /* A label for any 'Modified' text or title. For example to show the modification date of a file/folder. */ @@ -1624,6 +1624,10 @@ "Setup MEGA"="Configurer MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Pour profiter pleinement de votre compte MEGA, nous devons vous demander quelques autorisations."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 sur %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Autoriser l’accès à Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Aujourd’hui"; /* */ -"Yesterday"="Hier"; +"Yesterday"="Hier"; \ No newline at end of file diff --git a/iMEGA/Languages/he.lproj/Localizable.strings b/iMEGA/Languages/he.lproj/Localizable.strings index b0e0828447..35bd1fda31 100644 --- a/iMEGA/Languages/he.lproj/Localizable.strings +++ b/iMEGA/Languages/he.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 מתוך %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="היום"; /* */ -"Yesterday"="אתמול"; +"Yesterday"="אתמול"; \ No newline at end of file diff --git a/iMEGA/Languages/id.lproj/Localizable.strings b/iMEGA/Languages/id.lproj/Localizable.strings index a534776ab5..3b9e3c8965 100644 --- a/iMEGA/Languages/id.lproj/Localizable.strings +++ b/iMEGA/Languages/id.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Siapkan MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Untuk sepenuhnya memanfaatkan akun MEGA Anda, kami perlu menanyakan beberapa izin."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 dari %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Izinkan Akses ke Foto"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Hari ini"; /* */ -"Yesterday"="Kemarin"; +"Yesterday"="Kemarin"; \ No newline at end of file diff --git a/iMEGA/Languages/it.lproj/Localizable.strings b/iMEGA/Languages/it.lproj/Localizable.strings index ed18bcba93..72f748b425 100644 --- a/iMEGA/Languages/it.lproj/Localizable.strings +++ b/iMEGA/Languages/it.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Imposta MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Per avere un totale beneficio del tuo account MEGA abbiamo bisogno di chiederti alcuni permessi."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 di %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Permetti l'accesso alle foto"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Oggi"; /* */ -"Yesterday"="Ieri"; +"Yesterday"="Ieri"; \ No newline at end of file diff --git a/iMEGA/Languages/ja.lproj/Localizable.strings b/iMEGA/Languages/ja.lproj/Localizable.strings index 7e33d53c8d..55ed6b0c7b 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.strings +++ b/iMEGA/Languages/ja.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="MEGAをセットアップ"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="MEGAアカウントを十分にご活用いただくために、あなたに一部の権限をお尋ねする必要がございます。"; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%2のうち%1"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="写真へのアクセスを許可する"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="今日"; /* */ -"Yesterday"="昨日"; +"Yesterday"="昨日"; \ No newline at end of file diff --git a/iMEGA/Languages/ko.lproj/Localizable.strings b/iMEGA/Languages/ko.lproj/Localizable.strings index 8334d343bd..f495c8a10b 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.strings +++ b/iMEGA/Languages/ko.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="MEGA 설정"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="당신의 MEGA 계정의 원활한 이용을 위해, 우리는 몇 가지 권한 허용이 필요합니다."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%2 중 %1"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="사진첩에 대한 접근 허용"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="오늘"; /* */ -"Yesterday"="어제"; +"Yesterday"="어제"; \ No newline at end of file diff --git a/iMEGA/Languages/nl.lproj/Localizable.strings b/iMEGA/Languages/nl.lproj/Localizable.strings index ca34241bb3..edd80b90f9 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.strings +++ b/iMEGA/Languages/nl.lproj/Localizable.strings @@ -1589,7 +1589,7 @@ /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Twee-Stap Authenticatie Uitgeschakeld"; /* A message on the setup two-factor authentication page on the mobile web client. */ -"scanOrCopyTheSeed"="Scan or copy the seed to your Authenticator App. Be sure to backup this seed to a safe place in case you lose your device."; +"scanOrCopyTheSeed"="Scan of kopieer de seed naar uw Authenticator Applicatie. Zorg ervoor dat u een reservekopie maakt op een veilige plek voor het geval u uw apparaat verliest."; /* A button to help them restore their account if they have lost their 2FA device. */ "lostYourAuthenticatorDevice"="Bent u uw Authenticatie apparaat verloren?"; /* Settings section title where you can enable the option to 'Save Images in Photos' */ @@ -1624,6 +1624,10 @@ "Setup MEGA"="MEGA Instellen"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Om volledig voordeel te krijgen van uw MEGA account hebben we sommige toestemmingen nodig."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 van de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Verleen Toegang tot Foto's"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Vandaag"; /* */ -"Yesterday"="Gisteren"; +"Yesterday"="Gisteren"; \ No newline at end of file diff --git a/iMEGA/Languages/pl.lproj/Localizable.strings b/iMEGA/Languages/pl.lproj/Localizable.strings index f4a038ba89..28a451e3c3 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.strings +++ b/iMEGA/Languages/pl.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Skonfiguruj MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Aby w pełni wykorzystać swoje konto MEGA, musimy poprosić Cię o kilka uprawnień."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 z %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Zezwalaj na dostęp do zdjęć"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Dzisiaj"; /* */ -"Yesterday"="Przedwczoraj"; +"Yesterday"="Przedwczoraj"; \ No newline at end of file diff --git a/iMEGA/Languages/pt-br.lproj/Localizable.strings b/iMEGA/Languages/pt-br.lproj/Localizable.strings index 44388221bf..3a14d8e3f0 100644 --- a/iMEGA/Languages/pt-br.lproj/Localizable.strings +++ b/iMEGA/Languages/pt-br.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Configurar o MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para aproveitar ao máximo a sua conta no MEGA, precisamos de algumas permissões."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Permitir acesso ao Fotos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Hoje"; /* */ -"Yesterday"="Ontem"; +"Yesterday"="Ontem"; \ No newline at end of file diff --git a/iMEGA/Languages/ro.lproj/Localizable.strings b/iMEGA/Languages/ro.lproj/Localizable.strings index fe2a4f9b41..f57cf91fe1 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.strings +++ b/iMEGA/Languages/ro.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 din %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Astăzi"; /* */ -"Yesterday"="Ieri"; +"Yesterday"="Ieri"; \ No newline at end of file diff --git a/iMEGA/Languages/ru.lproj/Localizable.strings b/iMEGA/Languages/ru.lproj/Localizable.strings index 70468b5351..b0e020a488 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.strings +++ b/iMEGA/Languages/ru.lproj/Localizable.strings @@ -195,7 +195,7 @@ /* Title shown when a folder doesn't have any files */ "emptyFolder"="Папка пуста"; /* Title shown when your Cloud Drive is empty, when you don't have any files. */ -"cloudDriveEmptyState_title"="На вашем Облачном Диске нет файлов"; +"cloudDriveEmptyState_title"="В облачном диске нет файлов"; /* Button title shown in empty views when you can 'Add files' */ "addFiles"="Добавить файлы"; /* Title shown when you make a search and there is 'No Results' */ @@ -1624,6 +1624,10 @@ "Setup MEGA"="Настроить MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Чтобы обеспечить ваш аккаунт всеми преимуществами MEGA, мы должны попросить о некоторых разрешениях."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 из %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Разрешить доступ к фотографиям"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Сегодня"; /* */ -"Yesterday"="Вчера"; +"Yesterday"="Вчера"; \ No newline at end of file diff --git a/iMEGA/Languages/th.lproj/Localizable.strings b/iMEGA/Languages/th.lproj/Localizable.strings index b5f420894c..f5925bece1 100644 --- a/iMEGA/Languages/th.lproj/Localizable.strings +++ b/iMEGA/Languages/th.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="ตั้งค่า MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="เพื่อใช้ประโยชน์จากบัญชี MEGA ของคุณอย่างเต็มที่ เราจำเป็นต้องขอสิทธิ์บางอย่างแก่คุณ"; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 จาก %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="อนุญาตให้เข้าถึงรูปถ่าย"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="วันนี้"; /* */ -"Yesterday"="เมื่อวาน"; +"Yesterday"="เมื่อวาน"; \ No newline at end of file diff --git a/iMEGA/Languages/tl.lproj/Localizable.strings b/iMEGA/Languages/tl.lproj/Localizable.strings index b219e284e7..f77da6490f 100644 --- a/iMEGA/Languages/tl.lproj/Localizable.strings +++ b/iMEGA/Languages/tl.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="I-setup ang MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para magkaroon ng buong advantage sa iyong MEGA account kailangan naming hingin ang inyong permiso"; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 ng %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Bigyan ng Access sa Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Ngayon"; /* */ -"Yesterday"="Kahapon"; +"Yesterday"="Kahapon"; \ No newline at end of file diff --git a/iMEGA/Languages/tr.lproj/Localizable.strings b/iMEGA/Languages/tr.lproj/Localizable.strings index 1b8afe3efe..3ba7075946 100644 --- a/iMEGA/Languages/tr.lproj/Localizable.strings +++ b/iMEGA/Languages/tr.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="MEGA'yı kur"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="MEGA hesabınızın tüm avantajlarından yararlanabilmeniz için bazı izinler vermeniz gerekiyor."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Resimlere Erişime İzin Ver"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Bugün"; /* */ -"Yesterday"="Dün"; +"Yesterday"="Dün"; \ No newline at end of file diff --git a/iMEGA/Languages/uk.lproj/Localizable.strings b/iMEGA/Languages/uk.lproj/Localizable.strings index 730bfa602c..5cc14ea3eb 100644 --- a/iMEGA/Languages/uk.lproj/Localizable.strings +++ b/iMEGA/Languages/uk.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 з %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Сьогодні"; /* */ -"Yesterday"="Вчора"; +"Yesterday"="Вчора"; \ No newline at end of file diff --git a/iMEGA/Languages/vi.lproj/Localizable.strings b/iMEGA/Languages/vi.lproj/Localizable.strings index bc3ca188e2..7a2e2c344a 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.strings +++ b/iMEGA/Languages/vi.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 trong tổng %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="Hôm nay"; /* */ -"Yesterday"="Hôm qua"; +"Yesterday"="Hôm qua"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings index b7d1d5875a..884d31369d 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings @@ -321,7 +321,7 @@ /* The text is shown as an option on the details of a file or folder */ "saveForOffline"="离线储存"; /* List option shown on the details of a file or folder */ -"savedForOffline"="离线储存"; +"savedForOffline"="保存为离线"; /* Error message shown when a user tries to download a folder with the name Inbox on the main directory of the offline section */ "folderInboxError"="这个收件夹标签仅供Apple使用"; /* Title shown under the action that allows you to save an image to your camera roll */ @@ -976,7 +976,7 @@ /* Sentence shown under the available space to complete the info with the user account maximum storage. "available of {maximun storage}" */ "available of %@"="共%@ 可用"; /* Button title which triggers the action to upgrade your MEGA account level */ -"upgradeAccount"="付费升级账户"; +"upgradeAccount"="升级帐户"; /* */ "chooseYourAccountType"="选择您的账号类型"; /* Text shown under the button 'Skip' to explain that you can upgrade your account later in the section 'My Account'. */ @@ -1624,6 +1624,10 @@ "Setup MEGA"="安装MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="要充分利用您的MEGA帐户,我们需要得到您的允许。"; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="允许访问照片"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1681,9 +1685,9 @@ /* Text shown when an error occurs when trying to save a photo or video to Photos app */ "Could not save Item"="无法保存该项目"; /* Text shown for switching from thumbnail view to list view. */ -"List view"="列表视图"; +"List view"="列表显示"; /* Text shown for switching from list view to thumbnail view. */ -"Thumbnail view"="缩略视图"; +"Thumbnail view"="缩略图"; /* Message to inform the local user that someone has joined the current group call */ "%@ joined the call."="%@已加入通话。"; /* Message to inform the local user that someone has left the current group call */ @@ -1695,9 +1699,9 @@ /* Message shown in a chat room for a group call in progress displaying the duration of the call */ "Touch to return to call %@"="触摸即可返回通话%@"; /* Menu item to change from grid view to list view */ -"List view"="列表视图"; +"List view"="列表显示"; /* Menu item to change from list view to grid view */ -"Thumbnail view"="缩略视图"; +"Thumbnail view"="缩略图"; /* There are no notifications to display. */ "No notifications"="没有新通知"; /* The header of a notification related to payments */ @@ -1785,4 +1789,4 @@ /* */ "Today"="今天"; /* */ -"Yesterday"="昨天"; +"Yesterday"="昨天"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings index 95f5464dbf..37494ca5d5 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings @@ -1624,6 +1624,10 @@ "Setup MEGA"="Setup MEGA"; /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; +/* Message warning the user about the risk of not setting up permissions */ +"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +/* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ +"%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ "Allow Access to Photos"="Allow Access to Photos"; /* Detailed explanation of why the user should give permission to access to the photos */ @@ -1785,4 +1789,4 @@ /* */ "Today"="今天"; /* */ -"Yesterday"="昨天"; +"Yesterday"="昨天"; \ No newline at end of file diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index 6570a464f9..2a7fab4b6e 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -95,7 +95,13 @@ - (IBAction)setupButtonPressed:(UIButton *)sender { } - (IBAction)skipButtonPressed:(UIButton *)sender { - [self.delegate setupFinished]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"The MEGA app may not work as expected without permissions. Are you sure?", @"Message warning the user about the risk of not setting up permissions") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [self.delegate setupFinished]; + }]]; + [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"no", nil) style:UIAlertActionStyleCancel handler:nil]]; + + [self presentViewController:alertController animated:YES completion:nil]; } #pragma mark - MEGARequestDelegate diff --git a/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard b/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard index 80879c0400..d5f241c815 100644 --- a/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard +++ b/iMEGA/Utils/ViewControllers/OnboardingViewController/Onboarding.storyboard @@ -72,6 +72,12 @@ + + + + + + + + + + @@ -172,6 +180,7 @@ + diff --git a/iMEGA/Transfers/TransfersViewController.m b/iMEGA/Transfers/TransfersViewController.m index c9e3bcd77a..bd9214d199 100644 --- a/iMEGA/Transfers/TransfersViewController.m +++ b/iMEGA/Transfers/TransfersViewController.m @@ -53,7 +53,6 @@ - (void)viewDidLoad { [self.transfersSegmentedControl setTitle:AMLocalizedString(@"downloads", @"Downloads") forSegmentAtIndex:1]; [self.transfersSegmentedControl setTitle:AMLocalizedString(@"uploads", @"Uploads") forSegmentAtIndex:2]; - self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; } - (void)viewWillAppear:(BOOL)animated { @@ -131,30 +130,39 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - switch (section) { - case 0: - return self.transfers.count; - - case 1: - return self.uploadTransfersQueued.count; - - default: - return 0; + NSInteger numberOfRows = 0; + if (MEGAReachabilityManager.isReachable) { + switch (section) { + case 0: + numberOfRows = self.transfers.count; + break; + + case 1: + numberOfRows = self.uploadTransfersQueued.count; + break; + + default: + break; + } } + + return numberOfRows; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSInteger numberOfSections = 0; - switch (self.transfersSegmentedControl.selectedSegmentIndex) { - case UISegmentedControlNoSegment: - case AllSegmentIndex: - case UploadsSegmentIndex: - numberOfSections = 2; - break; - - case DownloadsSegmentIndex: - numberOfSections = 1; - break; + if (MEGAReachabilityManager.isReachable) { + switch (self.transfersSegmentedControl.selectedSegmentIndex) { + case UISegmentedControlNoSegment: + case AllSegmentIndex: + case UploadsSegmentIndex: + numberOfSections = 2; + break; + + case DownloadsSegmentIndex: + numberOfSections = 1; + break; + } } return numberOfSections; From 11691ea537f0502bf4bd82995eaa84dae882293e Mon Sep 17 00:00:00 2001 From: Javier Trujillo Date: Tue, 15 Jan 2019 11:28:38 +0100 Subject: [PATCH 57/58] Updated localizable strings --- iMEGA/Languages/Base.lproj/Localizable.strings | 2 +- iMEGA/Languages/ar.lproj/Localizable.strings | 2 +- iMEGA/Languages/de.lproj/Localizable.strings | 2 +- iMEGA/Languages/en.lproj/Localizable.strings | 2 +- iMEGA/Languages/es.lproj/Localizable.strings | 2 +- iMEGA/Languages/fr.lproj/Localizable.strings | 2 +- iMEGA/Languages/he.lproj/Localizable.strings | 2 +- iMEGA/Languages/id.lproj/Localizable.strings | 2 +- iMEGA/Languages/it.lproj/Localizable.strings | 2 +- iMEGA/Languages/ja.lproj/Localizable.strings | 2 +- iMEGA/Languages/ko.lproj/Localizable.strings | 4 ++-- iMEGA/Languages/nl.lproj/Localizable.strings | 2 +- iMEGA/Languages/pl.lproj/Localizable.strings | 2 +- iMEGA/Languages/pt-br.lproj/Localizable.strings | 2 +- iMEGA/Languages/ro.lproj/Localizable.strings | 2 +- iMEGA/Languages/ru.lproj/Localizable.strings | 2 +- iMEGA/Languages/th.lproj/Localizable.strings | 2 +- iMEGA/Languages/tl.lproj/Localizable.strings | 2 +- iMEGA/Languages/tr.lproj/Localizable.strings | 2 +- iMEGA/Languages/uk.lproj/Localizable.strings | 2 +- iMEGA/Languages/vi.lproj/Localizable.strings | 2 +- iMEGA/Languages/zh-Hans.lproj/Localizable.strings | 2 +- iMEGA/Languages/zh-Hant.lproj/Localizable.strings | 2 +- iMEGA/Launch/InitialLaunchViewController.m | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/iMEGA/Languages/Base.lproj/Localizable.strings index 849f0d041d..f192e18630 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/iMEGA/Languages/Base.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 of %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/ar.lproj/Localizable.strings b/iMEGA/Languages/ar.lproj/Localizable.strings index 2117ae8268..af77f6544a 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.strings +++ b/iMEGA/Languages/ar.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 من %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/de.lproj/Localizable.strings b/iMEGA/Languages/de.lproj/Localizable.strings index 5eaa927e03..88fe603f19 100644 --- a/iMEGA/Languages/de.lproj/Localizable.strings +++ b/iMEGA/Languages/de.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 von %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/en.lproj/Localizable.strings b/iMEGA/Languages/en.lproj/Localizable.strings index 849f0d041d..f192e18630 100644 --- a/iMEGA/Languages/en.lproj/Localizable.strings +++ b/iMEGA/Languages/en.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 of %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/es.lproj/Localizable.strings b/iMEGA/Languages/es.lproj/Localizable.strings index a1c3add6cf..74938fd5cb 100644 --- a/iMEGA/Languages/es.lproj/Localizable.strings +++ b/iMEGA/Languages/es.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para aprovechar al máximo tu cuenta MEGA, debemos pedirte algunos permisos."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="Es posible que la aplicación de MEGA no funcione correctamente sin los permisos necesarios. ¿Estás seguro?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/fr.lproj/Localizable.strings b/iMEGA/Languages/fr.lproj/Localizable.strings index 9f47818a88..68d9c56490 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.strings +++ b/iMEGA/Languages/fr.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Pour profiter pleinement de votre compte MEGA, nous devons vous demander quelques autorisations."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="L’appli MEGA pourrait ne pas fonctionner comme prévu sans les autorisations nécessaires. Êtes-vous certain ?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 sur %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/he.lproj/Localizable.strings b/iMEGA/Languages/he.lproj/Localizable.strings index 35bd1fda31..83eecebfcc 100644 --- a/iMEGA/Languages/he.lproj/Localizable.strings +++ b/iMEGA/Languages/he.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 מתוך %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/id.lproj/Localizable.strings b/iMEGA/Languages/id.lproj/Localizable.strings index 3b9e3c8965..01eeee1357 100644 --- a/iMEGA/Languages/id.lproj/Localizable.strings +++ b/iMEGA/Languages/id.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Untuk sepenuhnya memanfaatkan akun MEGA Anda, kami perlu menanyakan beberapa izin."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 dari %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/it.lproj/Localizable.strings b/iMEGA/Languages/it.lproj/Localizable.strings index 72f748b425..e36529b820 100644 --- a/iMEGA/Languages/it.lproj/Localizable.strings +++ b/iMEGA/Languages/it.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Per avere un totale beneficio del tuo account MEGA abbiamo bisogno di chiederti alcuni permessi."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="L'app di MEGA potrebbe non funzionare come aspettato senza i permessi di cui ha bisogno. Sei sicuro?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 di %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/ja.lproj/Localizable.strings b/iMEGA/Languages/ja.lproj/Localizable.strings index 55ed6b0c7b..597d11dd85 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.strings +++ b/iMEGA/Languages/ja.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="MEGAアカウントを十分にご活用いただくために、あなたに一部の権限をお尋ねする必要がございます。"; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="MEGAアプリは必要な権限がないと期待通りに動作しない可能性があります。よろしいですか? "; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%2のうち%1"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/ko.lproj/Localizable.strings b/iMEGA/Languages/ko.lproj/Localizable.strings index f495c8a10b..fb0c13427d 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.strings +++ b/iMEGA/Languages/ko.lproj/Localizable.strings @@ -1006,7 +1006,7 @@ /* Button on the Pro page to request a custom Pro plan because their storage usage is more than the regular plans. */ "requestAPlan"="요금제 요청"; /* Asks the user to request a custom Pro plan from customer support because their storage usage is more than the regular plans. */ -"thereAreNoPlansSuitableForYourCurrentUsage"="현재 사용량에 맞는 요금제가 없습니다. 개인화된 요금제는 [A]지원에 연락[/A]하세요."; +"thereAreNoPlansSuitableForYourCurrentUsage"="현재 사용량에 맞는 요금제가 없습니다. 맞춤 요금제는 [A]지원에 연락[/A]하세요."; /* Header shown to help on the purchasin process */ "selectMembership"="멤버쉽 선택:"; /* Footer shown to remenber that if you select a yearly plan yo will save up to 17% */ @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="당신의 MEGA 계정의 원활한 이용을 위해, 우리는 몇 가지 권한 허용이 필요합니다."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="MEGA 앱이 필요한 권한 없이는 예상과 다르게 작동할 수 있습니다. 확실합니까?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%2 중 %1"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/nl.lproj/Localizable.strings b/iMEGA/Languages/nl.lproj/Localizable.strings index edd80b90f9..6e20170e39 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.strings +++ b/iMEGA/Languages/nl.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Om volledig voordeel te krijgen van uw MEGA account hebben we sommige toestemmingen nodig."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 van de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/pl.lproj/Localizable.strings b/iMEGA/Languages/pl.lproj/Localizable.strings index 28a451e3c3..1ce1974abd 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.strings +++ b/iMEGA/Languages/pl.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Aby w pełni wykorzystać swoje konto MEGA, musimy poprosić Cię o kilka uprawnień."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="Aplikacja MEGA może nie działać zgodnie z oczekiwaniami bez wymaganych uprawnień. Jesteś pewny?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 z %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/pt-br.lproj/Localizable.strings b/iMEGA/Languages/pt-br.lproj/Localizable.strings index 3a14d8e3f0..3f34f92a77 100644 --- a/iMEGA/Languages/pt-br.lproj/Localizable.strings +++ b/iMEGA/Languages/pt-br.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para aproveitar ao máximo a sua conta no MEGA, precisamos de algumas permissões."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="O aplicativo do MEGA pode não funcionar como esperado sem as permissões necessárias. Você tem certeza?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 de %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/ro.lproj/Localizable.strings b/iMEGA/Languages/ro.lproj/Localizable.strings index f57cf91fe1..4bfd52b715 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.strings +++ b/iMEGA/Languages/ro.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 din %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/ru.lproj/Localizable.strings b/iMEGA/Languages/ru.lproj/Localizable.strings index b0e020a488..8b02ea021b 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.strings +++ b/iMEGA/Languages/ru.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Чтобы обеспечить ваш аккаунт всеми преимуществами MEGA, мы должны попросить о некоторых разрешениях."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 из %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/th.lproj/Localizable.strings b/iMEGA/Languages/th.lproj/Localizable.strings index f5925bece1..a603a78da1 100644 --- a/iMEGA/Languages/th.lproj/Localizable.strings +++ b/iMEGA/Languages/th.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="เพื่อใช้ประโยชน์จากบัญชี MEGA ของคุณอย่างเต็มที่ เราจำเป็นต้องขอสิทธิ์บางอย่างแก่คุณ"; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="แอป MEGA อาจไม่ทำงานอย่างที่คาดไว้ หากไม่มีการอนุญาตที่จำเป็น คุณแน่ใจหรือไม่"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 จาก %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/tl.lproj/Localizable.strings b/iMEGA/Languages/tl.lproj/Localizable.strings index f77da6490f..3599d32632 100644 --- a/iMEGA/Languages/tl.lproj/Localizable.strings +++ b/iMEGA/Languages/tl.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="Para magkaroon ng buong advantage sa iyong MEGA account kailangan naming hingin ang inyong permiso"; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 ng %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/tr.lproj/Localizable.strings b/iMEGA/Languages/tr.lproj/Localizable.strings index 3ba7075946..9a6db7a528 100644 --- a/iMEGA/Languages/tr.lproj/Localizable.strings +++ b/iMEGA/Languages/tr.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="MEGA hesabınızın tüm avantajlarından yararlanabilmeniz için bazı izinler vermeniz gerekiyor."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/uk.lproj/Localizable.strings b/iMEGA/Languages/uk.lproj/Localizable.strings index 5cc14ea3eb..f362f6166d 100644 --- a/iMEGA/Languages/uk.lproj/Localizable.strings +++ b/iMEGA/Languages/uk.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 з %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/vi.lproj/Localizable.strings b/iMEGA/Languages/vi.lproj/Localizable.strings index 7a2e2c344a..f6b09e9d8c 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.strings +++ b/iMEGA/Languages/vi.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 trong tổng %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings index 884d31369d..71c99ab33f 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hans.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="要充分利用您的MEGA帐户,我们需要得到您的允许。"; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="MEGA应用程序可能在无权限状态下无法正常工作。您确定要这样做吗?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings index 37494ca5d5..36392b275d 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings +++ b/iMEGA/Languages/zh-Hant.lproj/Localizable.strings @@ -1625,7 +1625,7 @@ /* Detailed explanation of why the user should give some permissions to MEGA */ "To fully take advantage of your MEGA account we need to ask you some permissions."="To fully take advantage of your MEGA account we need to ask you some permissions."; /* Message warning the user about the risk of not setting up permissions */ -"The MEGA app may not work as expected without permissions. Are you sure?"="The MEGA app may not work as expected without permissions. Are you sure?"; +"The MEGA app may not work as expected without the required permissions. Are you sure?"="The MEGA app may not work as expected without the required permissions. Are you sure?"; /* Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. */ "%1 of %2"="%1 / %2"; /* Title label that explains that the user is going to be asked for the photos permission */ diff --git a/iMEGA/Launch/InitialLaunchViewController.m b/iMEGA/Launch/InitialLaunchViewController.m index 2a7fab4b6e..73c9df4729 100644 --- a/iMEGA/Launch/InitialLaunchViewController.m +++ b/iMEGA/Launch/InitialLaunchViewController.m @@ -95,7 +95,7 @@ - (IBAction)setupButtonPressed:(UIButton *)sender { } - (IBAction)skipButtonPressed:(UIButton *)sender { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"The MEGA app may not work as expected without permissions. Are you sure?", @"Message warning the user about the risk of not setting up permissions") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:AMLocalizedString(@"attention", @"Alert title to attract attention") message:AMLocalizedString(@"The MEGA app may not work as expected without the required permissions. Are you sure?", @"Message warning the user about the risk of not setting up permissions") preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:AMLocalizedString(@"yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self.delegate setupFinished]; }]]; From 58a53aba7e4cb462a2cf3a03d92e97f617115ee7 Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Tue, 15 Jan 2019 11:48:09 +0100 Subject: [PATCH 58/58] Bump build version --- iMEGA/Extensions/MEGAPicker/Info.plist | 2 +- iMEGA/Extensions/MEGAPickerFileProvider/Info.plist | 2 +- iMEGA/Extensions/MEGAShare/Info.plist | 2 +- iMEGA/Info.plist | 2 +- iMEGA/Settings/About/AboutTableViewController.m | 2 +- iMEGA/Vendor/SDK | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iMEGA/Extensions/MEGAPicker/Info.plist b/iMEGA/Extensions/MEGAPicker/Info.plist index 2533863b26..350ec1ee2f 100644 --- a/iMEGA/Extensions/MEGAPicker/Info.plist +++ b/iMEGA/Extensions/MEGAPicker/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 4.4.2 CFBundleVersion - 135 + 136 NSExtension NSExtensionAttributes diff --git a/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist b/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist index 59c1ac9cf8..0b40f762d6 100644 --- a/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist +++ b/iMEGA/Extensions/MEGAPickerFileProvider/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 4.4.2 CFBundleVersion - 135 + 136 NSExtension NSExtensionFileProviderDocumentGroup diff --git a/iMEGA/Extensions/MEGAShare/Info.plist b/iMEGA/Extensions/MEGAShare/Info.plist index 3b2d5e839c..2534ff3740 100644 --- a/iMEGA/Extensions/MEGAShare/Info.plist +++ b/iMEGA/Extensions/MEGAShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 4.4.2 CFBundleVersion - 135 + 136 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/iMEGA/Info.plist b/iMEGA/Info.plist index 7923af37c5..7af8e9e9e3 100644 --- a/iMEGA/Info.plist +++ b/iMEGA/Info.plist @@ -51,7 +51,7 @@ CFBundleVersion - 135 + 136 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/iMEGA/Settings/About/AboutTableViewController.m b/iMEGA/Settings/About/AboutTableViewController.m index 3d1f097648..2fe3dd2786 100644 --- a/iMEGA/Settings/About/AboutTableViewController.m +++ b/iMEGA/Settings/About/AboutTableViewController.m @@ -39,7 +39,7 @@ - (void)viewDidLoad { self.versionCell.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; self.sdkVersionLabel.text = AMLocalizedString(@"sdkVersion", @"Title of the label where the SDK version is shown"); - self.sdkVersionSHALabel.text = @"a01c429d"; + self.sdkVersionSHALabel.text = @"975d2996"; self.megachatSdkVersionLabel.text = AMLocalizedString(@"megachatSdkVersion", @"Title of the label where the MEGAchat SDK version is shown"); self.megachatSdkSHALabel.text = @"9c9b9cce"; diff --git a/iMEGA/Vendor/SDK b/iMEGA/Vendor/SDK index a01c429d73..975d2996de 160000 --- a/iMEGA/Vendor/SDK +++ b/iMEGA/Vendor/SDK @@ -1 +1 @@ -Subproject commit a01c429d73af8a722c190108059efe7d1c859632 +Subproject commit 975d2996ded16ef695b460f652ad9f106b7e5d0e